PowerShell Script to build your Nano Server Image

This script is now retired! It has been replaced with the NanoServerImageGenerator.psm1 module on the latest Windows Server Technical Preview media. See Getting Started with Nano Server for instructions.



We’ve modified the script to ask for a SecureString as a means of accepting a password, as opposed to plain-text input. Instead of specifying a password string in the parameter list, the script will now prompt for a password securely. This follows more closely the security guidelines for PowerShell scripts and secure handling of credentials. Having said that, it is important to keep in mind that the mechanism via which the Administrator account’s password is set is still an Unattend.xml file. This means that the password for the Administrator account is stored in it, in plain-text, in the %WINDIR%\Panther directory after the script is finished. The password will remain there, in plain-text, until the first boot, when Nano Server will process the Unattend.xml file and delete the password from it.

Nano Server Image Manipulation

For our next installment in this series, we’re happy to announce a script that automates the customization of Nano Server images. This new PowerShell script allows you to convert a Nano Sever WIM image into a VHD disk, add packages to it, drivers and set configuration options. In this post, we’ll explore the script and what you can do with it.

Getting Started

First and foremost, the script is available for download as an attachment to this blog post. You will also need a companion script, which is available here (TechNet). Place these two scripts in the same directory and fire up PowerShell as administrator. Next, change directory into the folder where you’ve placed the scripts and dot-source the NanoServer.ps1 script:

. .\NanoServer.ps1

This will instruct PowerShell to read the contents of NanoServer.ps1 and allow access to its functionality within the current session. You’re now all set! The script ships with its own help, which you may access at any time (you may omit the window, of course):

Get-Help New-NanoServerImage -ShowWindow

First Steps

Let’s begin with a simple example. Suppose you have your Nano Server installation media on a network share and would like to create an image with a given computer name, the Hyper-V guest drivers as well as have Nano Server print its IP configuration to the screen on every boot. Here is how you can achieve that using the script:

. .\Nano.ps1

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\FirstSteps
  -ComputerName FirstStepsNano

The script will proceed as follows:

  1. Prompt for the Administrator password

  2. Copy the installation media from \\Path\To\Media\en_us into .\Base

  3. Convert the WIM image into a VHD

  4. Copy that VHD into .\FirstSteps

  5. Set that image’s administrator password as specified

  6. Set that image’s computer name to FirstStepsNano

  7. Install the Hyper-V guest drivers

  8. Make that image display the output of ipconfig on every boot.

The resulting image will be .\FirstSteps\FirstSteps.vhd. In addition, the script generates a log every time it runs; the script will let you know where it has placed it when it finishes. Similarly, since the WIM to VHD conversion is performed by the companion script, its own log can be found in %TEMP%\Convert-WindowsImage\<GUID> (where GUID is a unique identifier per conversion session). Lastly, when you run the script, assuming you always use the same base path, the conversion from WIM to VHD will occur only once. Further invocations of the script make copies of that VHD, so you can create as many custom images as necessary without having to wait for the conversion each time (unless the source WIM changes). Also, if your media does not change from invocation to invocation, you may omit the media path; the script will automatically make use of the files already present in the base path.

Including Workloads

Nano Server ships with several packages that enable additional functionality, such as acting as a Hyper-V host or as a storage server. With the new PowerShell script, these packages are easy to install. For instance, let’s inject the Hyper-V guest drivers and the storage package into a Nano Server image:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\IncludingWorkloads

In this case, the script will include the two specified packages as well as their corresponding language packages. These ship in the installation media in a subfolder whose name corresponds to the locale of the image, such as en_us, tr_tr, etc. By default, the script will attempt to install the language packages corresponding to the locale of the administrator account. So if your locale is fr_fr but are operating on a Swedish image, sv_se, the script will exit reporting that the French France packages are not there. To tell the script to use a different language, all you need to do is:

  -Language sv_se

Package Support

Here is a list of the switches and the corresponding workload packages:


File Server role and other storage components


Hyper-V Role


Failover Clustering


Variety of network and storage controller drivers.


Drivers for running Nano Server as a Hyper-V virtual machine.


Reverse forwarders allow you to run some software on Nano Server that is not explicitly made for Nano Server.

Extra Packages

If you happen to have additional packages that you would like to include in your Nano Server image, you may use the following syntax:

  -ExtraPackages .\Extras\Pack1.cab, .\Extras\Pack2.cab, .\Extras\ru_ru\LangPack1.cab

In the case of extra packages, the script will not attempt to install corresponding language packages. Thus, if you have language packages, remember to include them explicitly in the list, too. Also, make sure that the extra language packages that you install are the same language as the workload packages; the script won’t check.

Joining Domains

The script provides two ways of joining a domain. Both of them rely on offline domain provisioning, but the difference lies in the collection of the domain join blob. Let’s try an example where you’d like to join a Nano Server machine to a domain and see what happens:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\JoiningDomainsHarvest
  -ComputerName JoiningDomainsHarvest
  -DomainName Contoso

For this command, the script will proceed as follows with regards to domain provisioning:

  1. Harvest the domain join blob for the Contoso domain from the local machine (the local machine must be joined to the Contoso domain)

  2. Perform an offline provisioning of the image using that blob.

At the end of this operation, you will see that a computer named JoiningDomainsHarvest has appeared in your Active Directory computer list.

If you want to run the script on a computer that is not joined to the Contoso domain, you may harvest the blob on a machine that is, and tell the script to use that blob instead:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\JoiningDomainsNoHarvest
  -DomainBlobPath .\Path\To\Domain\Blob\JoiningDomainsNoHarvestContoso.djoin

To harvest a domain provisioning blob, you may use the following command:

  /Domain Contoso
  /Machine JoiningDomainsNoHarvest
  /SaveFile JoiningDomainsNoHarvest.djoin

Bear in mind that if you harvest a provisioning blob from another machine, you will have already included the computer name in it. Hence, if you try to add the computer name parameter to the script, it will report the error and exit.

Reusing Nodes

In the case that you already have a node with the same computer name as your future Nano Server image and don’t want to have to delete the name from Active Directory, you may have the script reuse it:


Injecting Drivers

Since Nano Server has no local facility to install drivers, it would be horrible if your exotic network adapter wasn’t supported. In order to include drivers of your choosing into a Nano Server image, you may use the following syntax:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\InjectingDrivers
  -DriversPath .\Extra\Drivers

The script will scout the directory for available drivers, recursively if necessary, and inject them into your image. In the folder where you keep your drivers, you will need the SYS files for the drivers as well as the corresponding INF files. You can have a look at an existing Windows installation under %WINDIR%\System32\DriverStore\FileRepository for inspiration. Note that Nano Server only supports 64-bit drivers, and they must be signed.

Connecting over WinRM

If you need to connect to your Nano Server computer over WinRM from a machine that is not in the same subnet, you will need to explicitly open port 5985 for inbound TCP traffic on the Nano Server image. The script helps you with that:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\ConnectingOverWinRM

Bring your own VHD

In the case that you already have a VHD image that you would like to customize, you may have the script use it with the following syntax:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\BYOVHD
  -ExistingVHDPath .\MyImage.vhd

With this command, the script will behave the same way, but instead of converting the original WIM to a VHD, or using the VHD that it may have already converted in a previous invocation, it will use the VHD image that you specify.

Emergency Management Services

To enable EMS on a Nano Server image, all you need is:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\EnablingEMS
  -EMSPort 3
  -EMSBaudRate 9600

This command will enable EMS on serial port 3 with a baud rate of 9600 bps. The port and baud rate parameters are optional, so if you don’t include them, they will be port 1 and 115200 bps respectively.

Kernel Debugging

In the hopes that you will never have to debug a live kernel, this section goes last. If you ever find yourself needing to do so, however, the script allows you to configure that option, too. There are four ways to debug a live kernel remotely: via serial port, over the network, 1394 (FireWire) and USB. If you need more information, please refer to this page. Let’s now have a look at each option.

Debugging over Serial Port

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\KernelDebuggingSerial
  -DebugMethod Serial
  -DebugCOMPort 1
  -DebugBaudRate 9600

As you’d expect, this command will enable serial debugging over port 2 with a baud rate of 9600 bps. As with EMS, the port and baud rate parameters are optional, and they are port 2 and 115200 bps by default. Bear in mind that if you enable both EMS and Kernel Debugging on the same image, you will need two different serial ports.

Debugging over the Network

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\KernelDebuggingNetwork
  -DebugMethod Net
  -DebugPort 64000

This command will enable kernel debugging over the network and the only computer that can connect has as its IP address. In addition, all communications will take place over port 64000. Both the IP and port parameters are mandatory, and the port number must be 49152 or higher. Moreover, since kernel debugging over the network is susceptible to interception, communication must be encrypted. When configuring kernel debugging on an image, the script automatically generates a key that you will need in order to connect. The script will place that key in a file next to the resulting VHD, and it will tell you about it when it finishes.

If you want to use your own key, you can use the -DebugKey parameter, as follows:

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\KernelDebuggingNetwork
  -DebugMethod Net
  -DebugPort 64000

Debugging over 1394 (FireWire)

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\KernelDebuggingFireWire
  -DebugMethod 1394
  -DebugChannel 3

This command will enable 1394 (FireWire) debugging and you will have to connect the remote debugger on channel 3. The channel parameter is mandatory.

Debugging over USB

  -MediaPath \\Path\To\Media\en_us
  -BasePath .\Base
  -TargetPath .\KernelDebuggingUSB
  -DebugMethod USB
  -DebugTargetName KernelDebuggingUSBNano

This command will enable debugging over USB. When you attempt to connect the remote debugger to the computer running the resulting image, you will need to specify the target name as it appears in the command.


This time around, we’ve introduced a PowerShell script that allows you to easily convert and customize Nano Server images. This script offers a variety of options, such as adding workload and extra packages, joining a domain, injecting drivers, opening a port for WinRM connections, customizing your own VHD, enabling EMS and enabling and configuring kernel debugging. All of these options can be mixed and matched to create the required configuration. The resulting VHD may be used without further effort.

We hope that you will find this tool useful and look forward to your feedback!

Hernan Gatta
Microsoft Intern


Comments (19)

  1. Manuel Henke says:

    Great script. But please release the script on GitHub.

  2. Jeff Hicks says:

    I’m curious why you are using the VHD format and not the newer VHDX. All my Hyper-V images now use this format. Or is there something specific to the VHD format with your script?

  3. Jeff Hicks says:

    You should also point out that you must run this script on Windows 10 (or Server 2016) because it requires the newer version of DISM. Even better would be to add that requirement to the script.

  4. Hernan Gatta says:

    Hi Jeff,

    There’s nothing preventing us from adding that option. For a first iteration, VHD seemed like the safest choice, given that VHDX images require Generation 2 VM’s in Hyper-V. It’s also possible to convert VHD images into VHDX ones using the Hyper-V VM Manager.
    Having said that, I’ll have a look and see if we can add that.

    For DISM, you make a good point; that’s why the script uses the version of DISM that ships on the Nano Server installation media, not the one on your machine. That way, the script can also run on Windows 8.1. Are you having trouble with it though?


  5. Jeff Hicks says:

    I would love for this to work from Windows 8.1. I was starting with the nano wim and packages already copied. But clearly I should try copying from source. And yes, having options for VHDX and a Gen2 VM would be ideal, but they should be separate parameters
    because I may want a GEN1 vm but a VHDX file. If you continue to develop this, I’d suggest turning it into a module.

  6. Jeff Hicks says:

    Now that I am running the complete command including copying files from media, I have a better understanding of what is going on. And to clarify something, Gen2 VMs may require a VHDX format, but a Gen1 machine can also use a VHDX file which is why those
    should be two different parameters.

  7. Jeff Hicks says:

    Guess I spoke too soon. Closer, but can’t this to complete on Windows 8.1 running PowerShell 4.0.

    $paramHash = @{
    MediaPath = ‘I:’
    BasePath = ‘C:NanoBase’
    TargetPath = ‘C:NanoImages’
    AdministratorPassword = ‘P@ssw0rd’
    ComputerName = ‘NANO-02’
    EnableIPDisplayOnBoot = $True
    Storage = $True
    GuestDrivers = $True

    New-NanoServerImage @paramHash

    This is the log:

    06/16/2015 14:27:21 & ‘C:NanoBaseToolsdism’ /Mount-Image /ImageFile:’C:NanoImagesNanoImages.vhd’ /MountDir:’C:NanoImagesMount’ -Index:1 /LogLevel:2 /LogPath:’C:UsersJeffAppDataLocalTempNew-NanoServerImage (DISM).log’
    06/16/2015 14:27:25 & ‘C:NanoBaseToolsdism’ /Add-Package /PackagePath:’C:NanoBasePackagesMicrosoft-NanoServer-Storage-Package.cab’ /Image:’C:NanoImagesMount’ /LogLevel:2 /LogPath:’C:UsersJeffAppDataLocalTempNew-NanoServerImage (DISM).log’
    06/16/2015 14:27:35 & ‘C:NanoBaseToolsdism’ /Add-Package /PackagePath:’C:NanoBasePackagesMicrosoft-NanoServer-Guest-Package.cab’ /Image:’C:NanoImagesMount’ /LogLevel:2 /LogPath:’C:UsersJeffAppDataLocalTempNew-NanoServerImage (DISM).log’
    06/16/2015 14:27:39 & ‘C:NanoBaseToolsdism’ /Add-Package /PackagePath:’C:NanoBasePackagesen-usMicrosoft-NanoServer-Storage-Package.cab’ /Image:’C:NanoImagesMount’ /LogLevel:2 /LogPath:’C:UsersJeffAppDataLocalTempNew-NanoServerImage (DISM).log’
    06/16/2015 14:27:44 & ‘C:NanoBaseToolsdism’ /Add-Package /PackagePath:’C:NanoBasePackagesen-usMicrosoft-NanoServer-Guest-Package.cab’ /Image:’C:NanoImagesMount’ /LogLevel:2 /LogPath:’C:UsersJeffAppDataLocalTempNew-NanoServerImage (DISM).log’
    06/16/2015 14:27:48 Exception calling "GetFullPath" with "1" argument(s): "The given path’s format is not supported."
    06/16/2015 14:27:48 Terminating due to an error. See log file at:

    06/16/2015 14:27:48 & ‘C:NanoBaseToolsdism’ /Unmount-Image /MountDir:’C:NanoImagesMount’ /Discard /LogLevel:2 /LogPath:’C:UsersJeffAppDataLocalTempNew-NanoServerImage (DISM).log’

    The target path gets deleted at the very end of the process, or something is deleting it and that causes the error. I can’t tell which.

  8. Hernan Gatta says:

    Hi Jeff,

    Thank you for your comment. That is indeed a bug where one call inside the script is assuming a relative path for TargetPath. If you try something like TargetPath = .Target1, it should work.

    I’ll get on it.

    Thanks again!

  9. Hernan Gatta says:

    @Jeff: It’s been fixed and a new copy uploaded. Give it a shot!

  10. Jeff Hicks says:

    That did the trick. Eventually I hope we can configure things like IP address settings via an unattend.xml. I have not had much luck getting that to work yet.

    And for anyone interested, here is a script to create the Nano server image, create the Hyper-V VM and start it.

    cd c:scripts
    . .NanoServer.ps1


    $pass = ConvertTo-SecureString -String ‘P@ssw0rd’ -AsPlainText -Force

    $paramHash = @{
    MediaPath = ‘I:’
    BasePath = ‘F:NanoBase’
    TargetPath = ‘F:NanoImages’
    AdministratorPassword = $pass
    ComputerName = ‘NANO-02’
    EnableIPDisplayOnBoot = $True
    Storage = $True
    GuestDrivers = $True
    EnableRemoteManagementPort = $True

    New-NanoServerImage @paramHash

    $original = Join-Path -Path $paramHash.TargetPath -ChildPath "$(Split-path $paramhash.TargetPath -leaf).vhd"
    $destination = (Join-path -path D:VHD -child "$($paramHash.ComputerName).vhd")
    Copy -Path $original -Destination $destination

    $newVMHash = @{
    Name = $paramHash.computername
    Generation = 1
    MemoryStartupBytes = 256MB
    SwitchName = "Work Network"
    VHDPath = $Destination

    New-VM @NewVMHash | Set-VM -DynamicMemory -MemoryMaximumBytes 512MB -Notes "Test Nano Server"
    Start-VM Nano-02

  11. JEET says:

    that was great article …i have few doubts regarding it …i have followed all the steps.but when i booted the vhd..it shows windos logon and loading ,shows ip address also .but ,it stays till and becomes blank screen.does we can manage it through gui
    or remote desktop …some tip we will be useful .thanx in advance.

  12. Jeff Hicks says:

    Jeet, there is no interactive console. You will have to use PowerShell remoting or other remote management tools from your desktop. This is explained in the Technet documentation. You could also ask for help in the Nano forum.


  13. Hernan Gatta says:

    Hi Jeet:

    Have a look at this article:

    While most of the steps in the guide are encapsulated in the script, the guide also explains how to manage Nano Server remotely.

    Thanks and thank you Jeff for answering.

  14. _Emin_ says:


    There’s a major issue with the script.
    There’s a function named Invoke-Command (@line 1210) inside the script and when you load the script NanoServer.ps1 by dot sourcing, the native invoke-command is overwritten in the global scope.

    Would you mind updating the script and naming your helper function differently?

    Here’s how to reproduce the issue:
    . ~/Downloads/NanoServer.ps1
    (dir Function:Invoke-Command).definition
    (gcm Microsoft.PowerShell.CoreInvoke-Command).definition


  15. Hernan Gatta says:

    Hi Emin,

    Thank you very much for pointing that out. It’s been fixed.

  16. aL says:

    This looks great! Pleaseo make a module out of it though and put it on the gallery 🙂 I’ve also found the convert-image script a bit unreliable, sometimes I works and some times not, it would be great to see it get some official Microsoft quality stamp

  17. anonymouscommenter says:

    Nano Server represents a great opportunity for applications to get the most of available resources and

  18. Hi,
    wouldn’t it make sense to give the user the Chance to select between MBR and GPT Partition style; this would enable UEFI Installation (currently the scritp contains fix "-VHDPartitionStyle MBR"

  19. anonymouscommenter says:

    Have you ever personally had to troubleshoot Nano Server but didn’t know where to start looking

Skip to main content