system.environment OSVersion property

I was using the Get-LatestWMF5 function in the ISE on a Windows 2012 R2 (PowerShell version 4.0 by default) and it downloaded the wrong file 😦

I was using the [system.environment] .Net class to get the version of the Operating System.
It works fine in the console but not in the ISE.

As you can see, it’s reported as 6.2 which means Windows 8/2012 although I’m on Windows 2012 R2.

There’s actually a change introduced the API that controls this behavior.
It’s documented on this page:
Windows PowerShell MVP Dave Wyatt provided this link. Thx Dave 🙂

I propose to replace the code that uses the


by this:

[Version]$((Get-CimInstance Win32_OperatingSystem).Version)

Get the latest WMF5

Michael Greene tweeted the following a few days ago

I love his idea and his code shows a few awesome tricks I ignored:

# Use shortcode to find latest TechNet download site
$confirmationPage = '' + $((invoke-webrequest '' -UseBasicParsing).links | ? Class -eq 'mscom-link download-button dl' | % href)
# Parse confirmation page and look for URL to file
$directURL = (invoke-webrequest $confirmationPage -UseBasicParsing).Links | ? Class -eq 'mscom-link' | ? href -match 'Win8.1AndW2K12R2-KB3134758-x64.msu' | % href | select -first 1
# Download file to local
$download = invoke-webrequest $directURL -OutFile $env:Temp\wmf5latest.msu
# Install quietly with no reboot
if (test-path $env:Temp\wmf5latest.msu) {
start -wait $env:Temp\wmf5latest.msu -argumentlist '/quiet /norestart'
else { throw 'the update file is not available at the specified location' }
# Clean up
Remove-Item $env:Temp\wmf5latest.msu
# Assumption is that the next likely step will be DSC config, starting with xPendingReboot to finish install
File names:
view raw WMF5Latest.ps1 hosted with ❤ by GitHub

I propose the following improvements:

  • Insert a require statement to tell people that the code is only v3 compatible. There’s no Invoke-Webrequest in PowerShell 2.0.
  • Download the required version based on the OS where you run the code. If you’ve PowerShell 3.0 on Windows 7 64bit, you’ll get the Windows6.1-KB2908075-x64.msu file, etc.

Let’s have a look at my proposal

#Requires -Version 3.0
Function Get-LatestWMF5 {
Begin {
$HT = @{
ErrorAction = 'Stop' ;
Verbose = $false ;
try {
$null = Get-Item $TargetFolder @HT
} catch {
Write-Warning -Message "The target folder specified as parameter does not exist"
Process {
try {
$p = Invoke-WebRequest -URI '' -UseBasicParsing @HT
} catch {
Write-Warning -Message "Failed to get webpage because $($_.Exception.Message)"
if ($p) {
try {
$d = Invoke-WebRequest -Uri $($p.BaseResponse.ResponseUri.OriginalString -replace 'details\.aspx','confirmation.aspx') -UseBasicParsing @HT
} catch {
Write-Warning -Message "Failed to get the content from webpage: $($p.BaseResponse.ResponseUri) because $($_.Exception.Message)"
Switch ([environment]::Is64BitOperatingSystem) {
$true {$bitness = '-x64' ; break}
$false {$bitness = '-x86' ; break}
Switch ([Version]$((Get-CimInstance Win32_OperatingSystem).Version)) {
{ $_ -gt [Version]'6.3' -and $_ -lt [Version]'6.4'} { $OS = 'WindowsBlue'; break }
{ $_ -gt [Version]'6.2' -and $_ -lt [Version]'6.3'} { $OS = 'Windows8' ; break }
{ $_ -gt [Version]'6.1' -and $_ -lt [Version]'6.2'} { $OS = 'Windows6.1' ; break }
if (-not($OS)) {
Write-Warning -Message "Theres's something wrong with the version of your Operating System. Aborting"
if ($d) {
$u = ($d.Links | Where {
$_.href -match ('{0}.*\{1}.msu'-f $OS,$bitness)
}).href | Select -First 1
Write-Verbose -Message "About to download the latest WMF 5 from $u"
try {
Invoke-WebRequest -Uri $u -UseBasicParsing @HT -OutFile (Join-Path -Path $TargetFolder -ChildPath $(([uri]$u).Segments[-1]))
Write-Verbose -Message "Successfully downloaded the latest WMF 5 to $TargetFolder"
Get-Item -Path (Join-Path -Path $TargetFolder -ChildPath $(([uri]$u).Segments[-1]))
} catch {
Write-Warning -Message "Failed to download $u because $($_.Exception.Message)"
End {}

On Windows 10, you don’t need this piece of code, so you’ll just get a warning:

On Windows 7 as of PowerShell 3.0, you get the following experience:

On Windows 2012 R2, you’ll experience the following:

Last warning: WMF 5.0 requires .Net Framework 4.5, make sure this requirement is met before installing the WMF 5.0.

First look at Nano Server

What is Nano server?

Nano server was first presented at the Build Conference

At #MSIgnite 2015 conference there were more presentations and a new ISO that contains Nano server (a.k.a. Windows Server Technical Preview 2) was published:

Some blog posts started to talk about it:

And the official “Getting Started with Nano Server” guide was published:


I’ve installed the Hyper-V feature on my Windows 10 laptop.
I’ve downloaded the Windows Server Technical Preview 2 iso file I mentioned above.
I’ve installed an Internal switch in Hyper-V and set a static IP Address as I don’t have a DHCP server.

New-VMSwitch -Name "Internal" -SwitchType Internal
# Disable DHCP
Get-NetAdapter -Name "vEthernet (Internal)" | 
Set-NetIPInterface -Dhcp:Disabled
# Set a static IP
Get-NetAdapter -Name "vEthernet (Internal)" | 
New-NetIPAddress -IPAddress -AddressFamily IPv4 -PrefixLength 24 -Verbose

If you double-click the Windows Server Technical Preview 2 iso file, it will be mounted and you’ll see:

Let’s quickly have a look at the NanoServer.wim image

I’ve created a script to provision Nano server virtual machines attached to the Internal switch from the ISO file. Every repeated task should be automated, right? 😉

Let’s see it in action:

When I booted the Nano server VM, I could see the output of the setupcomplete.cmd script that runs at the end of the OOBE phase.

I couldn’t apply the OfflineServicing phase to change the computername as it’s described in the getting stared guide. I’ve got error 0x80220001.
This means that the computer will be named ‘MINWINPC’.


I’ve also installed only 1 NiC and only loaded the package as it contains drivers for hosting Nano Server as a virtual machine.
Because the administrator password wasn’t changed through the unattend.xml file or the setupcomplete.cmd script, it means it’s blank.

These are the process running on the VM
Here’s the list of modules available on the Nano server I provisioned (remember, I didn’t load all the packages: Hyper-V, Failover Clustering and Storage)

Both memory and the size of the VHD file are about 512MB. If I had loaded all the packages, the image size would have grown to ~1GB.

Get-VM -Name Nano002 | Get-VMMemory
(Get-item (Get-VM -Name Nano002 | 
 Get-VMHardDiskDrive).Path).Length /1MB -as [int]


Nano Server looks very promising and it’s only the beginning.
Back in 2006, I created a WinPE 1.0 that was less that 100MB and as far as I remember, I was able to run some AV products, run Firefox, the minesweeper, the remote desktop client, load the firewall… and was also able to remote in the WinPE using the NT4 remote command server.
These days are dead but almost 10 years later Nano server is born with built-in PSRemoting. I believe it has a bright future and that miniaturization will carry on. It will for sure be a success because it’s robust, easily manageable (using PowerShell, DSC,…), cloud-optimized (scales more quickly, allows a better VM density), less vulnerable and exposed to security threats (attack surface is reduced, less security updates and reboots?) and will probably be the 1rst and a 1rst class cloud-OS for Windows Containers.

Minimal WSMan requirement to push locally a Desired State Configuration

I’ve been working on a deployment scenario where I’ll provision new computers from a PBR (Push Button Reset) image.

These laptops will run Windows 8.1 and the PBR image is actually a sysprep image that is configured to run a post-install script.

When the PC is provisionned or reset, the PBR image is applied/expanded to the C: drive and the post-install script is run at the end of the OOBE (Out-of-Box Experience) phase, just before the user can logon.

To configure Windows Updates settings, some registry keys, services,… DSC (Desired State Configuration) is the way to go as it’ll ensure the PC remains compliant even if there’s a drift later between 2 resets or/and the PC isn’t connected to any network.

As you may know DSC depends on WSMan and not on PSRemoting.
There’s a myth about PSRemoting that was uncovered by PowerShell Magazine and Windows PowerShell MVP Aleksandar Nikolic:

If I push a DSC config without configuring WSMan, I hit a wall and get this message:
The client cannot connect to the destination specified in the request.
Verify that the service on the destination is running and is accepting requests.
Consult the logs and documentation for the WS-Management service running on the destination, most commonly IIS or WinRM.
If the destination is the WinRM service, run the following command on the destination to analyze and configure the WinRM service: “winrm quickconfig”.
+ CategoryInfo : ConnectionError: (root/Microsoft/…gurationManager:String) [], CimException
+ FullyQualifiedErrorId : HRESULT 0x80338012
+ PSComputerName : localhost

I’d also get the above error message whenever the WinRM service is stopped or the WSMan listener is absent.

To fix it, I should run the following the Set-WSManQuickConfig because the computer isn’t joined to a domain.

Set-WSManQuickConfig -SkipNetworkProfileCheck

This would enable the WinRM firewall rule for the ‘Public’ profile and expose the WinRM to the localNetwork it’s connected to.
It would also set the LocalAccountTokenFilterPolicy registry value to remove the UAC remote restriction.

The above steps aren’t required to push locally (vs. over the wire) a DSC configuration.
The attack surface can actually be reduced so that the DSC configuration can only be pushed locally.
The non-domain joined Windows 8.1 PBR images can leverage DSC as soon as I:

# 1. Enable and start the WinRM service
Stop-Service -Name WinRM -PassThru | 
Set-Service -StartupType Automatic -PassThru | 

# 2. Enable and restrict the firewall rules 
# to localhost instead of LocalNetwork
Get-NetFirewallRule -Name @(
    'WINRM-HTTP-In-TCP', # Pubic
    'WINRM-HTTP-In-TCP-NoScope') | #Domain,Private
Enable-NetFirewallRule -PassThru | 
Get-NetFirewallAddressFilter | 
Set-NetFirewallAddressFilter -RemoteAddress ""

# 3. Add a listener (the firewall already enforces a restriction)
Get-ChildItem -Path WSMan:\localhost\Listener -Include listener* | 
Remove-Item -Recurse
New-WSManInstance winrm/config/Listener -SelectorSet @{

# 4. Disable Kerberos, not required in workgroup for local authentication
Set-Item -Path WSMan:\localhost\Service\Auth\Kerberos  -Value $false -Force
Set-Item -Path WSMan:\localhost\Service\Auth\Negotiate -Value $true  -Force

Voilà, my post-installation DSC configuration can be pushed locally whenever Windows 8.1 is reset on the device.