Pete Hinchley: Create a Nano Server Image using PowerShell

Here are a few PowerShell scripts for automating the creation of a new Windows Server 2016 Nano Server image.

The following code is used to create a folder structure suitable for creating new Nano Server images. The script creates a new parent folder named D:\Nano, with 4 sub-folders; Images for storing the new Nano Server images, Logs for storing the logs generated during the build process, Scripts for storing a custom post-deployment PowerShell script, and Updates for storing the latest Windows Server 2016 cumulative update cabinet.

# Path to Windows Server 2016 media.
$media = "D:\Media\Windows Server 2016\en_windows_server_2016_x64_dvd.iso"

# Path used for building Nano Server images.
$build = "D:\Nano"

# Mount media.
$mount = Mount-DiskImage -ImagePath $media -PassThru
$drive = ($mount | Get-Volume).DriveLetter + ":"

# Create the "build" folder.
New-Item -Type Directory -Path $build -Force | Out-Null

# Change into the "build" folder.
pushd $build

# Create required sub-folders.
New-Item -Type Directory -Path 'Images', 'Logs', Scripts', 'Updates' -Force | Out-Null

# Copy Nano Server media from mounted media.
Copy-Item "$drive\NanoServer" . -Recurse -Force

# Un-mount media.
Dismount-DiskImage -ImagePath $media

# Return to original directory.
popd

This next script, which should be named configure.ps1, and stored under the previously created Scripts folder, will be run immediately after the Nano Server image is created. It is used to configure the Windows Update client of the Nano Server to use a WSUS server (automatically installing available updates at 1am each Sunday).

# WSUS sever.
$wsus = "http://wsus.lab.hinchley.net:8530/"

# Windows Update registry key.
$key = "HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate"

# Configure Windows Update.
New-Item -Path $key\AU -Force | Out-Null
New-ItemProperty -Path $key -Name WUServer -Value $wsus -Type String -Force | Out-Null
New-ItemProperty -Path $key -Name WUStatusServer -Value $wsus -Type String -Force | Out-Null
New-ItemProperty -Path $key\AU -Name UseWUServer -Value 1 -Type Dword -Force | Out-Null
New-ItemProperty -Path $key\AU -Name AUOptions -Value 4 -Type Dword -Force | Out-Null
New-ItemProperty -Path $key\AU -Name ScheduledInstallDay -Value 1 -Type Dword -Force | Out-Null
New-ItemProperty -Path $key\AU -Name ScheduledInstallTime -Value 1 -Type Dword -Force | Out-Null

This next script is used to actually create the Nano Server as a VHDX image suitable for deployment as a second generation Hyper-V virtual machine. In addition to setting the name, edition, timezone, disk size, and IP details of the image, the script also applies the cumulative update stored within the Updates folder, and joins the server to the local Active Directory domain (i.e. the domain of the server on which this script is executed). The script also initiates the execution of the previously created post-deployment script to configure the Windows Update client.

You will be prompted to set the local administrator password when running the script.

Note: It is essential that the cumulative update to be applied to the Nano Server is already installed on the server on which this script is executed.

# Name of Nano Server.
$name = "NANO"

# Disk size.
$disksize = 20GB

# Timezone.
$timezone = "AUS Eastern Standard Time"

# IP configuration.
$ip = New-Object PSObject –Property @{
  'address' = "192.168.1.206";
  'mask'    = "255.255.255.0";
  'gateway' = "192.168.1.254";
  'dns'     = "192.168.1.200", "192.168.1.201";}

# Path used for building Nano images.
$build = "D:\Nano"

# Change into the "build" folder.
pushd $build

# Import the Nano Server image generator module.
Import-Module ".\NanoServer\NanoServerImageGenerator" -Verbose -Force

# Delete the existing image (if it exists).
if (Test-Path .\Images\$name.vhdx) { Remove-Item .\Images\$name.vhdx -Force }

# Delete the existing logs (if they exists).
if (Test-Path .\Logs\$name\DISM.log) { Remove-Item .\Logs\$name\* -Force }

# Get the name of the latest cumulative update from the Updates folder.
$update = (Get-ChildItem .\Updates)[0].Name

# Create the Nano Server image.
New-NanoServerImage -DeploymentType Guest -Edition Standard -MediaPath . -BasePath .\Base -TargetPath .\Images\$name.vhdx -ComputerName $name -DomainName $env:userdnsdomain -ReuseDomainNode -EnableRemoteManagementPort -InterfaceNameOrIndex Ethernet -Ipv4Address $ip.address -Ipv4SubnetMask $ip.mask -Ipv4Gateway $ip.gateway -Ipv4Dns $ip.dns -MaxSize $disksize -CopyPath .\Scripts -SetupCompleteCommand "tzutil.exe /s `"$timezone`"", 'powershell.exe -file "C:\Scripts\configure.ps1"' -ServicingPackagePath .\Updates\$update -LogPath .\Logs\$name

# Return to original directory.
popd

This final script is used to create a new Hyper-V virtual machine from the VHDX image.

# Name of Nano Server.
$name = "NANO"

# Path to Nano Server image.
$image = "D:\Nano\Images\$name.vhdx"

# Path to Hyper-V VMs.
$vms = "D:\VMs"

# Name of VM switch.
$switch = "LAB"

# Create a folder for the new Nano Server.
New-Item -Type Directory "$vms\$name" -Force | Out-Null

# Copy the Nano Server image to the VM folder.
Copy-Item $image "$vms\$name\$name.vhdx" -Force

# Create the VM.
New-VM -VHDPath "$vms\$name\$name.vhdx" -Generation 2 -MemoryStartupBytes 1GB -Name $name -Path $vms -SwitchName $switch

# Start the VM.
Start-VM -Name $name

One last thing. To check the cumulative update was successfully installed, establish a PowerShell session with the new Nano Server, and run the following command. The name of the update should be listed.

$ci = New-CimInstance -Namespace root/Microsoft/Windows/WindowsUpdate -ClassName MSFT_WUOperationsSession
$result = $ci | Invoke-CimMethod -MethodName ScanForUpdates -Arguments @{SearchCriteria="IsInstalled=1";OnlineScan=$true}
$result.Updates | select -expandproperty Title