New-Nano: automating Windows Nano Server lab builds with PowerShell

I wrote this function to automate Nano Server lab builds into Hyper-V. You need to supply the “Windows Server 2016 Technical Preview 5” ISO, which you can get here or here. My script is based partially on ideas presented in the Microsoft TechNet article “Getting Started with Nano Server“.

You will need to have Hyper-V on your computer, have a Hyper-V switch on a network that has DHCP running somewhere (if you have a switch in Hyper-V defined as “External”, this should work fine). You will also need to supply a working directory.

New-Nano copies the required files from the ISO, runs Microsoft’s cmdlet New-NanoServerImage to build a Hyper-V disk image, creates a new VM from that disk image, starts the VM, and gets network information from the VM. The function then runs an example PowerShell Session to demonstrate connectivity to the new Nano VM. Total build time in my tests is a little over three minutes.

If you look to the bottom of the script, you will see an example of how to call the New-Nano function.

Further refinements would be to install features and packages to the Nano, and to do domain joins. Please note that this should only be used for a lab environment because the administrator password is in plaintext. If this was going into a production environment, you would want to encode it.

Enjoy. Please try it out and leave a comment.

Start-Transcript -Path "C:\Lab\NewNano.txt"
function New-Nano
{
    [CmdletBinding()]
    Param
    (
        # Path to the Windows Server 2016 ISO
        [Parameter(Mandatory=$true)]
        $ImagePath,

        # Working Directory
        [Parameter(Mandatory=$true)]
        $WorkingDir,

        # Admin Password
        [SecureString]
        $AdminPassword = (ConvertTo-SecureString -AsPlainText "Password1" -Force),

        # Hyper-V Switch Name
        [String]
        $NetworkSwitch = "External",

        # Nano VM Name
        [String]
        $Name = "nano" + $(Get-Random -Minimum 1000 -Maximum 9999),
        
        # Processor Count
        $ProcessorCount = 1,

        # Memory Startup Bytes
        $MemoryStartupBytes = 512MB
    )

    Process
    {
    # ref: https://technet.microsoft.com/en-us/library/mt126167.aspx
    
    $VirtualHardDiskPath = $(Get-VMHost).VirtualHardDiskPath
    $VirtualMachinePath = $(Get-VMHost).VirtualMachinePath
    $NewNanoPath = $VirtualHardDiskPath + $Name + ".vhdx"
    $StartingDir = (Get-Location).Path
    $MountObject = Mount-DiskImage -ImagePath $ImagePath -PassThru
    $MountDriveLetter = ($MountObject | Get-Volume).DriveLetter + ":"
    $NanoMount = $MountDriveLetter + "\NanoServer\"

    # copy the NanoServer dir to the working dir
    $NanoDir = $WorkingDir + "NanoServer\"
    Copy-Item -Path $NanoMount -Destination $NanoDir -Recurse -Force

    Set-Location $NanoDir

    Import-Module .\NanoServerImageGenerator -Verbose

    # create the VHD
    $NewNanoResult = New-NanoServerImage -DeploymentType Guest -Edition Standard -MediaPath $MountDriveLetter -BasePath .\Base -TargetPath $NewNanoPath -EnableRemoteManagementPort -ComputerName $Name -AdministratorPassword $AdminPassword

    # new VM from the VHD
    New-VM -Name $Name -VHDPath $NewNanoPath -MemoryStartupBytes $MemoryStartupBytes -SwitchName $NetworkSwitch -BootDevice VHD -Generation 2 -Path $VirtualMachinePath  |
    Set-VM -DynamicMemory -ProcessorCount $ProcessorCount -Passthru -AutomaticStopAction ShutDown -AutomaticStartAction Nothing 

    # finish up.
    $MountObject | Dismount-DiskImage
    "Starting $Name"
    Start-VM $Name

    while ((Get-VM $Name).State -ne "Running")
        {Start-Sleep 5}
    "$Name is Running."

    while ((Get-VM $Name).NetworkAdapters.Status[0] -ne "Ok") 
        {Start-Sleep 5}
    "Network adapter reporting Ok."

    $vm = Get-VM $Name
    $vm.NetworkAdapters | select VMName, SwitchName, Status, IPAddresses

    "Demonstrate that PowerShell Session works"
    $AdminUser = "$Name\Administrator"
    $cred = New-Object -Typename System.Management.Automation.PSCredential  -Argumentlist $AdminUser, $AdminPassword
    $pss = New-PSSession -ComputerName $Name -Credential $cred
    Invoke-Command -Session $pss -ScriptBlock {Get-Service | where Status -EQ "Running"| Format-Table} 
    Remove-PSSession $pss

    # return to original directory
    Set-Location $StartingDir
    }
}

$NewNanoParams = @{
    ImagePath = "C:\ISOs\en_windows_server_2016_technical_preview_5_x64_dvd_8512312.iso";
    WorkingDir = "C:\Lab\";
    AdminPassword =  (ConvertTo-SecureString -AsPlainText "Password1" -Force);
    NetworkSwitch = "External"
    Name = "LabNano" + (Get-Random -Minimum 100 -Maximum 999);
    ProcessorCount = 1;
    MemoryStartupBytes = 512MB
    }

New-Nano @NewNanoParams

Stop-Transcript