The last month I have presented how to generate a NanoServer VHDX image from the Windows Server 2016 Technical Preview 2 ISO. If you have read the topic, you have seen that there are a lot of steps. So I have decided to create a PowerShell script to generate NanoServer VHDX image and I want to share it in this topic.
This script enables me to save lot of time and avoid me to make mistake. There are some requirements to run this script. If you have never generated a VHDX from NanoServer.wim file, I recommend you to read this post. So to run my script you need:
- An unattend.xml file ready;
- A SetupComplet.cmd file ready;
- The Windows Server 2016 TC2 ISO downloaded;
- The Convert-WindowsImage.ps1 downloaded and copied in the same folder than my script (you can download this script here)
Unattend.xml file example
Bellow you can find an example of the Unattend.xml file. I use it to create my VHDX image:
<?xml version='1.0' encoding='utf-8'?> <unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="https://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"> <settings pass="offlineServicing"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <ComputerName>NanoServer04</ComputerName> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <UserAccounts> <AdministratorPassword> <Value>password </Value> <PlainText>true</PlainText> </AdministratorPassword> </UserAccounts> <TimeZone>Romance Standard Time</TimeZone> </component> </settings> <settings pass="specialize"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <RegisteredOwner>Romain Serre</RegisteredOwner> <RegisteredOrganization>Tech-Coffee</RegisteredOrganization> </component> </settings> </unattend>
SetupComplete.cmd file example
You need also a SetupComplete.cmd file to run some commands on first boot. I use the below script in my lab:
netsh advfirewall set all state off netsh interface ip set address "Ethernet" static 10.10.0.203 255.255.255.0 10.10.0.1 netsh interface ipv4 add dnsserver "Ethernet" address=10.10.0.5 index=1 hostname ipconfig
Script to generate the NanoServer VHDX image
Below you can find the script that I have written. Copy the script in New-NSVHDX.ps1 file.
#requires -version 4.0 #requires –runasadministrator <# .SYNOPSYS This script enables to create Nano Server VHDX image from the NanoServer.wim file .VERSION 0.1: Initial version 0.2: Change in the script to work with Convert-WindowsImage.ps1 Version 10 .AUTHOR Romain Serre Blog: //www.tech-coffee.net Twitter: @RomSerre .PARAMETERS WorkFolder: Specify the work folder where will be stored WIM, VHDX, Dism and so on VHDXName: Specify the VHDX file name (without .vhdx) ISOLiteralPath: Specify the absolute path to the Windows Server 2016 ISO UnattendFile: Specify the absolute path to your unattend.xml file SetupCompleteFile: Specify the absolute path to SetupComplete.cmd file .EXAMPLE New-NSVHDX -WorkFolder C:\temp\NanoPrep ` -VHDXName NanoServer01 ` -ISOLiteralPath "C:\Temp\10074.0.150424-1350.fbl_impressive_SERVER_OEMRET_X64FRE_EN-US.ISO" ` -UnattendFile "c:\temp\unattend.xml" ` -SetupCompleteFile "c:\temp\SetupComplete.cmd" #> ##### Parameters ##### # ---------------------------------------------------------------- [CmdletBinding()] param( [Parameter(Mandatory=$True, HelpMessage='Specify the work folder where will be stored WIM, VHDX, Dism and so on')] [Alias('WorkFolder')] [string]$ParentFolder, [Parameter(Mandatory=$True, HelpMessage='Specify the VHDX file name (without .vhdx)')] [Alias('VHDXName')] [String]$OutputImageName, [Parameter(Mandatory=$True, HelpMessage='Specify the absolute path to the Windows Server 2016 ISO')] [Alias('ISOLiteralPath')] [String]$ISOPath, [Parameter(Mandatory=$True, HelpMessage='Specify the absolute path to your unattend.xml file')] [Alias('UnattendFile')] [String]$UnattendFilePath, [Parameter(Mandatory=$True, HelpMessage='Specify the absolute path to SetupComplete.cmd file')] [Alias('SetupCompleteFile')] [String]$SetupCompleteFilePath ) ##### Variables ##### # ---------------------------------------------------------------- $Vnb = "0.2" # Convert-WindowsImage script name $ConvertWindowsImageScript = "Convert-WindowsImage.ps1" # Set SubFolder name $DismFolder = "Dism" $VHDXFolder = "VHDX" $PkgFolder = "Packages" $MountFolder = "MountDir" $WIMFolder = "WIM" ##### NanoServer Packages list #####. # Comment packages that you don't want to add to the final VHDX # ---------------------------------------------------------------- $PackagesList = @( "Microsoft-NanoServer-Compute-Package.cab", "Microsoft-NanoServer-OEM-Drivers-Package.cab", "Microsoft-NanoServer-FailoverCluster-Package.cab", "Microsoft-NanoServer-Guest-Package.cab", "Microsoft-NanoServer-Storage-Package.cab" ) ##### Main Code (do not modify if you don't know what you do) ##### # ----------------------------------------------------------------- # SubFolders in the WorkSpace $SubFolders = @( "\$DismFolder", "\$VHDXFolder", "\$PkgFolder", "\$MountFolder", "\$WIMFolder" ) # Get script path $ScriptPath = Split-Path -Parent $PSCommandPath $($ScriptPath + "\$ConvertWindowsImageScript") Clear Write-Host " ################################################################" -ForeGroundColor Green Write-Host " # Welcome to the Nano Server VHDX image generator $Vnb #" -ForeGroundColor Green Write-Host " # This script has been written by Romain Serre #" -ForeGroundColor Green Write-Host " # Twitter: @RomSerre #" -ForeGroundColor Green Write-Host " # Blog: //www.tech-coffee.net #" -ForeGroundColor Green Write-Host " ################################################################" -ForeGroundColor Green Write-Host Write-Host # Veryfing if Convert-WindowsImage.ps1 is present if (!(Test-Path $($ScriptPath + "\$ConvertWindowsImageScript"))){ Write-Host "$((Get-Date -Format g)) - Can't find $ConvertWindowsImageScript. Please copy $ConvertWindowsImageScript in the same directory that this script." -ForeGroundColor Red Exit } . $($ScriptPath + "\$ConvertWindowsImageScript") # Veryfing if ISO file exists if (!(Test-Path $ISOPath)){ Write-Host "$((Get-Date -Format g)) - Can't find $ISOPath. Please specify a valid path to the Windows Server 2016 ISO." -ForeGroundColor Red Exit } # Veryfing if unattend.xml file exists if (!(Test-Path $UnattendFilePath)){ Write-Host "$((Get-Date -Format g)) - Can't find $UnattendFilePath. Please specify a valid path to the unattend.xml file." -ForeGroundColor Red Exit } # Veryfing if SetupComplete.cmd file exists if (!(Test-Path $SetupCompleteFilePath)){ Write-Host "$((Get-Date -Format g)) - Can't find $SetupCompleteFilePath. Please specify a valid path to SetupComplete.cmd file." -ForeGroundColor Red Exit } if (!(Test-Path $ParentFolder)){ Write-Host "$((Get-Date -Format g)) - Working folder doesn't exist. Creating working folder" -ForeGroundColor Cyan New-Item -ItemType Directory -Path $ParentFolder > $Null } Else { Write-Host "$((Get-Date -Format g)) - Working folder already exists. Skipping creation" -ForeGroundColor Cyan } # Preparing Subfolder in workspace Write-Host "$((Get-Date -Format g)) - Creating subfolder in workspace ..." -ForeGroundColor Cyan Foreach ($SubFolder in $SubFolders){ if (!(Test-Path $($ParentFolder + $SubFolder))){ Write-Host "$((Get-Date -Format g)) - Creating $($ParentFolder + $SubFolder) sub folder" -ForeGroundColor Cyan New-Item -ItemType Directory -Path $($ParentFolder + $SubFolder) > $null } Else { Write-Host "$((Get-Date -Format g)) - $($ParentFolder + $SubFolder) already exists. Skipping creation." -ForeGroundColor Cyan } } # Preparing NanoServer VHDX name $OutputImageName = $OutputImageName + ".vhdx" Write-Host "$((Get-Date -Format g)) - The VHDX image will be called $OutputImageName" -ForegroundColor Cyan # Copying requirements from Windows Server 2016 ISO Write-Host "$((Get-Date -Format g)) - Mounting $ISOPath ..." -ForeGroundColor Cyan Mount-DiskImage $ISOPath $DriveLetter = (Get-DiskImage $ISOPath | Get-Volume).DriveLetter Write-Host "$((Get-Date -Format g)) - The ISO is mounted on $($DriveLetter):\" -ForegroundColor Cyan $ISONanoPath = $DriveLetter + ":\NanoServer" $ISOSourcePath = $DriveLetter + ":\Sources" Write-Host "$((Get-Date -Format g)) - Copying NanoServer.wim to $($ParentFolder + "\$WIMFolder")" -ForeGroundColor Cyan Copy-Item -Path $($ISONanoPath + "\NanoServer.wim") -Destination $($ParentFolder + "\$WIMFolder") -For Write-Host "$((Get-Date -Format g)) - Copying Packages folder to $($ParentFolder + "\$PkgFolder")" -ForeGroundColor Cyan Copy-Item -Path $($ISONanoPath + "\Packages\*") -Destination $($ParentFolder + "\$PkgFolder") -Recurse -Force Write-Host "$((Get-Date -Format g)) - Copying api*downlevel*.dll to $($ParentFolder + "\$DismFolder")" -ForeGroundColor Cyan Copy-Item -Path $($ISOSourcePath + "\api*downlevel*.dll") -Destination $($ParentFolder + "\$DismFolder") -Force Write-Host "$((Get-Date -Format g)) - Copying *dism* to $($ParentFolder + "\$DismFolder")" -ForeGroundColor Cyan Copy-Item -Path $($ISOSourcePath + "\*dism*") -Destination $($ParentFolder + "\$DismFolder") -Force Write-Host "$((Get-Date -Format g)) - Copying *provider* to $($ParentFolder + "\$DismFolder")" -ForeGroundColor Cyan Copy-Item -Path $($ISOSourcePath + "\*provider*") -Destination $($ParentFolder + "\$DismFolder") -Force Dismount-DiskImage $ISOPath Write-Host "$((Get-Date -Format g)) - The ISO is dismounted" -ForegroundColor Cyan # Converting WIM file to VHDX Write-Host "$((Get-Date -Format g)) - Converting NanoServer.wim into VHDX for Gen2 Virtual Machines... " -ForegroundColor Cyan Convert-WindowsImage -SourcePath $($ParentFolder + "\$WIMFolder\NanoServer.wim") ` -VHD $($ParentFolder + "\$VHDXFolder\$OutputImageName") ` -Edition "CORESYSTEMSERVER_INSTALL" > $Null Write-Host "$((Get-Date -Format g)) - Mounting $($ParentFolder + "\$VHDXFolder\$($OutputImageName)") in $($ParentFolder + "\$MountFolder")" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /Mount-Image /ImageFile:$($ParentFolder + "\$VHDXFolder\$($OutputImageName)") /index:1 /MountDir:$($ParentFolder + "\$MountFolder") > $null Foreach ($Package in $PackagesList){ Write-Host "$((Get-Date -Format g)) - Adding $Package to the $($ParentFolder + "\$VHDXFolder\$($OutputImageName)")" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /add-Package /PackagePath:$($ParentFolder + "\$PkgFolder\$Package") /Image:$($ParentFolder + "\$MountFolder") > $null cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /add-Package /PackagePath:$($ParentFolder + "\$PkgFolder\en-us\$Package") /Image:$($ParentFolder + "\$MountFolder") > $null } Write-Host "$((Get-Date -Format g)) - Committing change and unmounting image" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /Unmount-Image /Mountdir:$($ParentFolder + "\$MountFolder") /commit > $Null # Applying the unattend.xml file to the image Write-Host "$((Get-Date -Format g)) - Mounting $($ParentFolder + "\$VHDXFolder\$($OutputImageName)") in $($ParentFolder + "\$MountFolder")" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /Mount-Image /ImageFile:$($ParentFolder + "\$VHDXFolder\$($OutputImageName)") /index:1 /MountDir:$($ParentFolder + "\$MountFolder") > $null Write-Host "$((Get-Date -Format g)) - Applying $UnattendFilePath" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /image:$($ParentFolder + "\$MountFolder") /Apply-Unattend:$UnattendFilePath > $null Write-Host "$((Get-Date -Format g)) - Creating $($ParentFolder + "\$MountFolder\windows\panther")" -ForegroundColor Cyan New-Item -Type Directory -Path $($ParentFolder + "\$MountFolder\windows\panther") > $null Write-Host "$((Get-Date -Format g)) - Copying $UnattendFilePath to $($ParentFolder + "\$MountFolder\windows\panther")" -ForegroundColor Cyan Copy-Item $UnattendFilePath $($ParentFolder + "\$MountFolder\windows\panther") Write-Host "$((Get-Date -Format g)) - Committing change and unmounting image" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /Unmount-Image /Mountdir:$($ParentFolder + "\$MountFolder") /commit > $null # Copy the SetupComplete.cmd file to the image Write-Host "$((Get-Date -Format g)) - Mounting $($ParentFolder + "\$VHDXFolder\$($OutputImageName)") in $($ParentFolder + "\$MountFolder")" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /Mount-Image /ImageFile:$($ParentFolder + "\$VHDXFolder\$($OutputImageName)") /index:1 /MountDir:$($ParentFolder + "\$MountFolder") > $null Write-Host "$((Get-Date -Format g)) - Creating $($ParentFolder + "\$MountFolder\windows\Setup\Scripts")" -ForegroundColor Cyan New-Item -Type Directory -Path $($ParentFolder + "\$MountFolder\windows\Setup\Scripts") > $null Write-Host "$((Get-Date -Format g)) - Copying $SetupCompleteFilePath to $($ParentFolder + "\$MountFolder\windows\Setup\Scripts")" -ForegroundColor Cyan Copy-Item $SetupCompleteFilePath $($ParentFolder + "\$MountFolder\windows\Setup\Scripts") Write-Host "$((Get-Date -Format g)) - Committing change and unmounting image" -ForegroundColor Cyan cmd.exe /c "$ParentFolder\$DismFolder\dism.exe" /Unmount-Image /Mountdir:$($ParentFolder + "\$MountFolder") /commit > $null Write-Host "Your NanoServer VHDX image is ready in $($ParentFolder + "\$VHDXFolder\$($OutputImageName)")" -ForegroundColor Cyan Write-Host Write-Host Write-Host "Thank you to have used this script. Please if you have got some issues, make a feedback :)" -ForegroundColor Green
Use the script to generate the image
First copy the script and Convert-WindowsImage.ps1 in the same folder.
If you want manage the packages that will be added to the image, you can edit the script and modify the $PackagesList array to remove or to add packages (around line 69).
Then open a PowerShell command in RunAs Administrator mode and run the below command. Make sure that your ExecutionPolicy is set to RemoteSigned or Unrestricted by using the cmdlet Get-ExecutionPolicy. You can set the execution policy by using Set-ExecutionPolicy cmdlet.
.\New-NSVHDX -WorkFolder C:\temp\NanoFolder ` -VHDXName NanoServer04 ` -ISOLiteralPath "C:\Temp\10074.0.150424-1350.fbl_impressive_SERVER_OEMRET_X64FRE_EN-US.ISO" ` -UnattendFile "c:\temp\NanoPrep\unattend.xml" ` -SetupCompleteFile "c:\temp\NanoPrep\SetupComplete.cmd"
Below the explanation of each parameter:
- WorkFolder: this is the folder where Dism, VHDX, WIM and packages will be stored;
- VHDXName: this is the name of the final VHDX file;
- ISOLiteralPath: specify the absolute path to the Windows Server 2016 TC2 ISO;
- UnattendFile: specify the absolute path to the unattend.xml;
- SetupCompleteFile: Specify the absolute path to the SetupComplete.cmd.
When the script is finished you should have something like that:
Verifying if the VHDX image works
Now that the VHDX is generated, you can create a virtual machine and attach the VHDX:
Next I start the Virtual Machine and the commands added to the SetupComplete.cmd should run on first boot:
Great it’s working J. So now I’m trying to connect to the server with PowerShell:
It’s good I can connect to my NanoServer04 J.
Bonjour Romain,
Many thanks for this blog post, I’ve successfully run through the script and generated the VHDX file. When I attempt to boot the VM I get prompted for a Bitlocker Key! I’m running client hyper-v on win10 build 10130 was this my mistake 😉
Kind Regards,
Warren
Hi Warren,
Thank you for the feedback 🙂
Hey Romain,
Really thankful for this blog post been looking for a tutorial like this for a while, I tried to do this but, for some reason when I run the New-NSVHDX.ps1 script, it gets to converting nanoserver.wim into VHDX for Gen2 Virtual Machines and then it hangs, Do you think you could help with this?
Whoops, I figured it out, I download a new copy of the Convert-WindowsImage.ps1 and it has an issue on line 4092 and I forgot to fix it, but I did and now it works perfectly, Thanks Again!
Okay so different issue now, I run the script to make the VHDX it runs almost perfectly, it says everything works and that the VHDX has been made but when I go to the folder where it is suppose to be, there is nothing. I search my whole computer nothing, so it says it makes it. But it doesn’t actually do that. Any idea what the issue could be?
Hi Peter,
Did you run the script in RunAs Administrator ? Could you verify if the WIM has been copied in the WIM folder as well as packages files, dism and so on ?
I have not tested yet the new convert-WIndowsImage with my script. To verify if it’s working you can remove the “> $Null” at line 185.
Thank you Peter.
Romain.
Yeah I made sure to run the script as an Administrator, and yeah I checked everything is copied into its respective folder underneath the Parent Folder. I did remove the “> $Null” at line 185 and it does the same thing, everything seems like it works and says the VHDX is created but when i go and check in the folder there is no VHDX.
Hi Peter,
I have found the problem. It is because COnvert-WindowsImage has been updated to version 10. I modify my script to work with the new version of Convert-WindowsImage and I post again.
Thank you for your feedback 🙂
Hi Peter,
I have updated the code of New-NSVHDX.ps1. Could you try it ? (I suggest you to copy all and past it)
Thank you peter.
Romain.
It worked perfectly!! Thank you so much for the help
Great news ! Thanks for the feedback
Just a small feedback : execute Set-ExecutionPolicy with unrestricted or remote before trying to run your script (because it’s not signed)
https://technet.microsoft.com/library/hh847748.aspx
Hi Stan,
I have modified this blog post to take into consideration the PowerShell execution policy.
Thank you 🙂
Romain.