New-NanoServerVHD Updated to support changes in Convert-WindowsImage

A new version of Convert-WindowsImage.ps1 script that me New-NanoServerVHD.ps1 script uses was released a few days ago. It fixes the issue with running on Windows 10 and Windows Server 2016. However it was also changed in other ways that caused my New-NanoServerVHD.ps1 script to no longer function.

So I’ve updated the New-NanoServerVHD.ps1 script to support the newer ConvertWindowsImage.ps1 script. I also added support for creating a VHDx (with a GPT partition table format) so that it can be used with Generation 2 VMs.

You can control the format of the VHD that is created by passing in a VHDFormat parameter. This parameter defaults to VHD. If VHD is created it will automatically have a partition format of MBR set. When a VHDx is created it will have a GPT partition format.

Here is an example showing how to create a Nano Server VHDx for a Gen 2 VM:

 .\New-NanoServerVHD.ps1 ` 
            -ServerISO 'c:\nano\10074.0.150424-1350.fbl_impressive_SERVER_OEMRET_X64FRE_EN-US.ISO' ` 
            -DestVHD c:\nano\NanoServer02.vhdx `
            -VHDFormat VHDx `
            -ComputerName NANOTEST02 ` 
            -AdministratorPassword 'P@ssword!1' ` 
            -Packages 'Storage','OEM-Drivers','Guest' ` 
            -IPAddress ''

The updated New-NanoServerVHD.ps1 script can be downloaded here.


Setting the Computer Name installing Nano Server bug resolved

When I originally wrote the script to help install Nano Server I ran into a problem where I couldn’t get the Computer Name of the Nano Server to set during the OfflineServicing phase. This was supposed to be a new feature of Windows Server 2016 where the computer name could now be set in this phase rather than having to wait for the Specialize phase – which meant one less reboot during installation of the OS – saving precious seconds. And when installing Nano, saving an extra few seconds actually matter. I spent some time trying to get this new feature to work but nothing I tried worked, so I resorted to setting it in the Specialize phase like installations of old.

But thanks to Michael Birtwistle for pointing out this forum post. It points out that the Unattend.xml file in the original Nano Server instructions from Microsoft was incorrect. So I have corrected the Install-NanoServerVHD.ps1 script to reflect this as well. So even faster Nano Server provisioning should be available now.

Installing Windows Management Framework 5.0 with a GPO

The Need

Last week I decided I needed to get to know the new features that come with DSC in the new Windows Management Framework 5.0 (aka PowerShell 5.0) April Preview release (available to download here). I figured I’d also need to look at updating my DSCTools module to use the new features as well. But first up I’d need to update all my lab machines with the WMF 5.0 update. Being a proper lazy nerd I thought I’d just automate it.

The Problem

After downloading the .MSU file for installing WMF 5.0 from Microsoft I decided that WSUS would be the proper way to get this out to all my machines. Nope. Wrong! WSUS only supports pushing updates that appear in the Microsoft Update Catalogue (looking for Windows Management Framework) – but no update packages greater than Windows Management Frameworks 2.0 are available in the catalogue:

Where is PowerShell 5.0? Or 4.0? Or 3.0?

Where is PowerShell 5.0? Or 4.0? Or 3.0?

As an aside after doing a bit of reading on the reason for this it appears that updating to later versions of PS can cause problems with some applications and so it is kept as a manual process.

Next on the drawing board was push the update out using GPO’s software installation policies – except it only supports MSI files. So that won’t work (although converting an MSU to an MSI might be possible according to some sources).

The Solution

Finally I settled on pushing the MSU onto the machines by using a GPO Startup Script – of the PowerShell flavour of course. It seemed like I could just adapt the PS script I wrote for installing Office 2013 via GPO. After a bit of coding I had the update installing (in silent mode of course).

The next problem was that I needed some sort of registry key that could be checked to see if the update was already installed so it didn’t try to repeatedly reinstall every time the computer started up. I spent a few hours hunting around for information on where in the registry a record of installed updates was kept and couldn’t seemed to find any.

So instead I just used a simple WMI query to find out if the update was installed:

get-wmiobject -Class win32_QuickFixEngineering -Filter "HotfixID = 'KB2908075'"

The above command will return a list of KBs installed with a matching HotfixID. There will be 0 if the KB is not installed, so to use it I needed to count the objects returned:

(get-wmiobject -Class win32_QuickFixEngineering -Filter "HotfixID = 'KB2908075'" | Measure-Object).Count -gt 0

If the MSU or EXE (did I mention this script will work with hotfixes in EXE form) did indeed need installing the script just builds a command line and calls it:

If ([io.path]::GetExtension($InstallerPath) -eq '.msu') {
    [String]$Command="WUSA.EXE $InstallerPath /quiet /norestart)"
    [String]$Type="MSU $KBID"
} else {
    [String]$Command="$InstallerPath /quiet /norestart"
    [String]$Type="EXE $KBID"

# Call the product Install.
& cmd.exe /c "$Command"
[Int]$ErrorCode = $LASTEXITCODE

The Script

So after putting all the pieces together I had a finished script that seemed to do exactly what I needed. It can be downloaded from the Microsoft Script Center PowerShell Scripts to Install Application (EXE) or Update (MSU) using GPO. It is also available in my GitHub repo (edit: I moved this out of my generic PowerShell tools repo and into it’s own repo).


Before getting started, I strongly suggest you read my post about the problems encountered using PowerShell parameters in GPO here. There are some really annoying issues with PowerShell script parameters in GPO that will have you tearing your hair out – luckily I tore my hair out for you.

Using It

  1. Download the PowerShell Scripts to Install Application (EXE) or Update (MSU) using GPO from Microsoft Script Center.
  2. Download the MSU/EXE update file and put it in a shared folder all of your machines can get to. I used \\plague-pdc\Software$\Updates\WindowsBlue-KB3055381-x64.msu
  3. Create a new GPO that will be used to install the update – I called mine Install WMF 5.0 Policy:New Policy
  4. Locate the Startup setting under Computer Configuration/Policies/Windows Settings/Scripts:The Startup Script of a Computer GPO.
  5. Double click the Startup item to edit the scripts and select the PowerShell Scripts tab:
    Setting the Startup PowerShell Scripts
  6. Click the Show Files button and copy the Install-Update.ps1 file that was in the zip file downloaded from Microsoft Script Center into this location:GPO Folder containing script
  7. Close the folder and go back to the Startup Properties window and click the Add button.
  8. Set the Script Name to Install-Update.ps1.
  9. Set the Script Parameters to be the following (customized the bold sections to yourenviroment):
     -InstallerPath "\\plague-pdc\Software$\Updates\WindowsBlue-KB3055381-x64.msu" -KBID "KB3055381" -LogPath \\plague-pdc\LogFiles$\

    Note: You can leave off the -LogPath parameter if you don’t want to create logs stating if the update was installed correctly on each machine.

    Add the script and parameters

  10. Click OK.
  11. Click OK on the Startup Properties window.
  12. Close the GPME window and assign the policy to whichever OUs you want to try WMF 5.0 out on.

That was probably a lot more detail than most people needed, but I thought I throw it in there just in case.

If you require more details on the parameters available in the script, use the PowerShell cmdlet Get-Help .\Install-Update.ps1 in the folder that Install-Update.ps1 is installed into – or just look at the PowerShell Scripts to Install Application (EXE) or Update (MSU) using GPO Microsoft Script Center page.

Installing an Application Script

While I was at it I also decided that I wanted to install applications contained in an EXE by this method too (Notepad++ was my excuse). This required a slightly different approach to detect if the application was already installed. Basically depending on the application a registry key and/or value needs to be checked to see if the application needs installation. These registry entries differ for every application being installed so which keys to check for an app need to be passed in via parameters to the PowerShell script.

For example, for Notepad++ version the registry key HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Notepad++ must exist and requires a string value called DisplayVersion to be equal to “”.

I also wrote a script, Install-Application.ps1 to do all this as well, and it is available in the same package as the Install-Update.ps1 script. I will write a separate blog post on using this one as it can be a little bit trickier thanks to the limitations with passing parameters to PS scripts in GPOs. So I’ll leave this post till next week.

Thanks for reading!

PowerShell Paramters in GPO Scripts


This morning I decided I wanted to update all my lab servers to Windows Management Framework 5.0 so I could do some work on the new DSC features that come with it. To do this, I though I’d use a GPO with a startup PowerShell script that would perform the installation of the WMF 5.0 April hotfix (available here).

A GPO Startup PowerShell script with parameters.

A GPO Startup PowerShell script with parameters.

On thinking about this I decided it might also be a good idea to modify the PowerShell script designed to install Microsoft Office 2013 products via GPO (see the post here). After producing the new scripts and testing them by manually running them to ensure they worked correctly, I put them into some GPOs.  And that is when things started to go wrong!

Parameter Problems

The issue I ran into was that the parameters set in the GPO PowerShell script parameters seemed to misbehave in several ways. After about 6 hours of investigating and testing I’ve found the following things cause problems when you do them.

Parameter Length Limit

There seems to be a maximum number of characters that will be used in the Script Parameters setting in a GPO Startup/Shutdown/Logon/Logoff PowerShell script. The limit appears to be 207 but I can’t find official documentation of this. If script parameters longer than this limit is entered the additional characters will simply be ignored, leading to the script either failing to run or running incorrectly.

If you do run into this issue, one way around it is to edit the script and wrap all the code in a function definition and then add a call with the parameters to the end of the script after the end of the function definition. For Example:

Function Install-Application {
... Existing script code here ...
Install-Application -InstallerPath "\\Server\Software$\Notepad++\npp." -InstallerParameters "/S" -RegistryKey "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Notepad++" -RegistryName "DisplayVersion" -RegistryValue "" -LogPath \\Server\Logfiles$\

The PowerShell script parameters in the GPO can then be dropped as they are contained in the script itself – this is not ideal, but unless Microsoft lifts this limitation it may be required.

Parameter Quotes

There is also some odd behaviour passing parameters with quotes (single or double) to the PowerShell scripts in a GPO. I have run into several situations where the use of quotes causes the parameters to either not be passed to the script or passed with additional quotes in them. I recommend the following:

  1. DO NOT use single quotes around any parameters – use double quotes around string parameters only.
  2. DO NOT end the parameter list with a quoted parameter – this seems to cause the last parameter content to contain an extra quote.


In short, if you stick to the above when calling PowerShell scripts with parameters from GPO then you might save yourself a lot of time scratching your head.

As a quick aside, the scripts I wrote as part of this (for installing Windows QFE Hotfixes and Applications via GPO) are available on Microsoft Script Center here. I will be writing a full post on these scripts later in the week.