Deploy WMF5.0 on Windows 7

When the WMF5.0 was republished, I’ve asked the following question in the comments

Can you please explain why WMF 4.0 is a prerequisite on Windows Server 2008 R2 and Windows 7 SP1? Is it related to the WMI repository and/or to DSC?

Krishna C Vutukuri from the Windows PowerShell Team replied

As you may know, WMF 5 uses CBS based technology for installation. Win 7 SP1/W2K8R2 systems contain PowerShell 2.0, WinRM, WMI by default. After Win7/W2K8 R2 released, we shipped WMF 3.0 and WMF 4.0 which contain updates to these features. Installing / Uninstalling these packages uncovered some issues in the upgrade options from inbox -> WMF4 and inbox->WMF3->WMF4. We fixed all those issues in WMF4. Hence we had to make WMF4 as requirement for installing WMF5 on Win7 SP1/W2K8 R2. Here are the specific issues you might face if you do not install WMF4 before upgrading to WMF5:

(a) Forwarded Events log is unavailable and EventCollector log is not displayed in Event Viewer after you uninstall in Windows 7 SP1 and in Windows Server 2008 R2 SP1
(b) Issues with PSModulePath environment when you upgrade directly from inbox to WMF5 or from WMF 3 to WMF5.

Again WMF4 addresses these issues and our internal testing environment uses this matrix for testing WMF5 on Win7 SP1/W2K8R2 SP1.

Thanks
Krishna
Windows PowerShell Team

The other person interested in the upgrade path to WMF 5.0 on Windows 7 asked about
WMF4-update

To summarize: Windows 7 SP1 is shipped with PowerShell 2.0 and .Net 3.5 SP1 and to upgrade it to PowerShell 5.0, you’ve to:

  1. fullfil the .Net 4.x requirement
  2. update to WMF 4.0
  3. reboot
  4. update to WMF 5.0
  5. reboot

Ok, .Net 4.5.2 or .Net 4.6.x and WMF 5.0 can deployed from Microsoft Update but how to deploy WMF 4.0?

I’m not the only one who noticed this shortcoming. Bjorn Houben says on his blog:

One of the disadvantages however is that for operating systems before Windows 8.1/2012, a prerequisite is that WMF4 is installed, which cannot easily be deployed using WSUS unfortunately.

Martin Schvartzman @MSFT discussed how to deploy WMF 4.0 on this page.

Although I’ve got ConfigMgr in my environment, I wanted to find a solution

  • that doesn’t have any dependency (except Internet connectivity),
  • that would deploy .Net 4.5.2 if missing but that wouldn’t fail if it has .Net 4.6 or 4.6.1,
  • that would minimize the number of restarts required,
  • that will install the required components silently and not force a restart

My code somehow illustrates the concept of immutable infrastructure that Jeffrey Snover talked about in a recent video.

[…] All your infrastructure is the result of a base set of components and an algorithm to produce a result. If you don’t like the result or ever need to make a change, you never change the instance, you go back, change the recipe, re-run it to produce the instance. […]

My code is a recipe to ease the pain of getting Windows 7 from its built-in PowerShell 2.0 to WMF 5.0.
It’ll drive you through the following different stages:

    • Run it for the first time with all components missing, it will install .Net 4.5.2
    • wmf2towmf5-stage1

      Re-run it and it moves to the next step and installs WMF 4.0
      wmf2towmf5-stage2

    • Re-run it and it will kindly tell you that a reboot is pending
    • wmf2towmf5-reboot1

    • Re-run it after the 1st restart, it will move on and install WMF 5.0
    • wmf2towmf5-stage3

    • Re-run it after the 2nd restart and it will do nothing except telling you that WMF 5.0 is already installed
    • wmf2towmf5-final

Here’s the code (gone through the PSScriptAnalyzer module that only flagged it for using the Get-WmiObject cmdlet)

[CmdletBinding()]
Param()
Begin {
if ($pshome -like '*syswow64*') {
Write-Verbose 'Restarting script under 64-bit Shell'
$PSScriptRoot = Split-path -parent $MyInvocation.MyCommand.Definition
& (Join-Path -Path ($pshome -replace "syswow64", "sysnative") -ChildPath 'powershell.exe') -File `
(Join-Path -Path $PSScriptRoot -ChildPath $MyInvocation.mycommand) @args
exit
}
$sb = New-Object -TypeName 'System.Text.StringBuilder'
$PSBoundParameters.GetEnumerator() | ForEach-Object {
if ($_.Value -is [switch]) {
$null = $sb.Append("-$($_.Key) ")
} else {
# if the value is an array of objects this won't work
$null = $sb.Append("-$($_.Key) $($_.Value) ")
}
}
Write-Verbose "Flat version of PSBoundparameters is: $sb"
try {
$os = [Version]$((Get-WmiObject -Class Win32_OperatingSystem -ErrorAction Stop).Version)
} catch {
Write-Warning -Message 'Failed to query WMI Win32_OperatingSystem Class'
}
# Run only on Windows 7
if ($os -eq [system.version]'6.1.7601') {
Write-Verbose "Running an expected OS version $([system.environment]::OSVersion.Version.ToString())"
} else {
Write-Warning "Unexpected OS version $([system.environment]::OSVersion.Version.ToString())"
break
}
$id = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent();
if (-not($id.IsInRole(544))) {
Write-Warning -Message 'Not running as Administrator'
$WshShell = New-Object -ComObject Shell.Application
$PSScriptRoot = Split-path -parent $MyInvocation.MyCommand.Definition
$WshShell.ShellExecute(
(Join-Path -Path ($pshome -replace 'syswow64','sysnative') -ChildPath 'powershell.exe'),
"-NoProfile -ExecutionPolicy Bypass -File $(Join-Path -Path $PSScriptRoot -ChildPath $MyInvocation.mycommand) $sb",
$null,
'runas'
)
exit
} else {
Write-Verbose -Message 'Running with administrative privileges'
}
# Define our local log file for post-mortem debugging
$OFHT = @{
Filepath = "$($env:windir)\temp\WMF5-PSinstall.$('{0:yyyyMMddHHmmss}' -f (Get-Date)).log";
Append = $true ;
NoClobber = $true ;
Encoding = 'ascii' ;
}
}
Process {
# Avoid install if magic file is found
if (Test-Path -Path "$($env:systemroot)\NoWMF5.dat" -PathType Leaf) {
Write-Warning -Message "NoWMF5.dat magic file found in systemroot"
"$((get-date).ToString('s')) ; NoWMF5.dat magic file found in systemroot" | Out-File @OFHT
break
}
# Test if WMF5.0 is installed
if ($PSVersionTable.PSVersion -gt [version]'5.0') {
Write-Verbose -Message 'WMF 5.0 already installed'
"$((get-date).ToString('s')) ; WMF 5.0 already installed" | Out-File @OFHT
break
}
# Registry path that indicates that a PS version was installed and a reboot is pending
$PSPendingPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending\*PowerShell*'
# Prepare a hashtable for Start-Process
$SPHT = @{
NoNewWindow = $true ;
PassThru = $true ;
Wait = $true ;
ErrorAction = 'Stop';
}
# Hardcoded download URL
$DotNetURI = 'http://wsus.ds.download.windowsupdate.com/c/msdownload/update/software/ftpk/2015/01/ndp452-kb2901983-x86-x64-enu_0350e593835125031f36e846ff3b936c09b8d479.exe'
$WMF4URI = 'https://download.microsoft.com/download/3/D/6/3D61D262-8549-4769-A660-230B67E15B25/Windows6.1-KB2819745-x64-MultiPkg.msu'
$WMF5URI = 'http://go.microsoft.com/fwlink/?LinkId=717504'
# Test if we already run WM4.0
if ($PSVersionTable.PSVersion -eq [version]'4.0') {
if (-not( Get-ChildItem -Path $PSPendingPath -ErrorAction SilentlyContinue)) {
# STAGE 3: WMF4 already installed > install WMF5
$MSU = (Join-Path -Path $($env:systemroot) -ChildPath 'temp\Win7AndW2K8R2-KB3134760-x64.msu')
$WMF5HT = @{
Uri = $WMF5URI ;
OutFile = $MSU ;
UseBasicParsing = $true ;
UserAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko' ;
}
try {
Invoke-WebRequest @WMF5HT -ErrorAction Stop -Verbose:$false
Write-Verbose -Message 'Successfully downloaded WMF 5.0'
"$((get-date).ToString('s')) ; Successfully downloaded WMF 5.0" | Out-File @OFHT
} catch {
Write-Warning -Message "Failed to download WMF 5.0 because $($_.Exception.Message)"
"$((get-date).ToString('s')) ; Failed to download WMF 5.0 because $($_.Exception.Message)" | Out-File @OFHT
}
#SHA256 077E864CC83739AC53750C97A506E1211F637C3CD6DA320C53BB01ED1EF7A98B
if ((Get-AuthenticodeSignature -FilePath $MSU -ErrorAction SilentlyContinue).Status.value__ -ne 0) {
Write-Warning -Message 'Signature from WMF5.0 file downloaded is not valid'
"$((get-date).ToString('s')) ; Signature from WMF5.0 file downloaded is not valid" | Out-File @OFHT
break
}
Write-Verbose -Message 'Launching the installation of WMF5.0'
"$((get-date).ToString('s')) ; Launching the installation of WMF5.0" | Out-File @OFHT
$SPHT.Add('FilePath',"$($env:systemroot)\system32\wusa.exe")
$SPHT.Add('ArgumentList',@($MSU,'/quiet','/norestart' ))
try {
Start-Process @SPHT
} catch {
Write-Warning -Message "Failed to start the installation of WMF5.0 because $($_.Exception.Message)"
"$((get-date).ToString('s')) ; Failed to start the installation of WMF5.0 because $($_.Exception.Message)" |
Out-File @OFHT
}
} else {
Write-Warning -Message 'WMF5.0 is installed and a reboot is pending'
"$((get-date).ToString('s')) ; WMF5.0 is installed and a reboot is pending" | Out-File @OFHT
}
} else {
# Import Bits cmdlets required for both Stage 1 and 2
try {
Import-Module -Name BitsTransfer -Force -Verbose:$false -ErrorAction Stop
} catch {
Write-Warning -Message "Failed to load BitsTransfer module because $($_.Exception.Message)"
"$((get-date).ToString('s')) ; Failed to load BitsTransfer module because $($_.Exception.Message)" | Out-File @OFHT
break
}
# Test the .Net Prerequisite
if (Test-Path -Path "$($env:systemroot)\Microsoft.Net\FrameWork\v4.0.30319\System.Runtime.dll") {
if( (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4.0\Client' -Name Install).Install -ne 1) {
Write-Warning -Message 'Double check .Net 4.x prerequisite failed'
"$((get-date).ToString('s')) ; Double check .Net 4.x prerequisite failed" | Out-File @OFHT
break
}
if (-not( Get-ChildItem -Path $PSPendingPath -ErrorAction SilentlyContinue)) {
# STAGE 2: .Net 4.5.2 already installed > install WMF 4.0
$MSU = Join-Path -Path $($env:systemroot) -ChildPath 'temp\Windows6.1-KB2819745-x64-MultiPkg.msu'
# Download with BITS as we are running PS 2.0 or 3.0 and don't have yet Invoke-WebRequest cmdlet
try {
$job = Start-BitsTransfer -Suspended -Asynchronous -Source $WMF4URI -Destination $MSU -ErrorAction Stop
$null = Resume-BitsTransfer -BitsJob $job -Asynchronous -ErrorAction Stop
} catch {
Write-Warning -Message "Failed to initiate BITS transfer because $($_.Exception.Message)"
"$((get-date).ToString('s')) ; Failed to initiate BITS transfer because $($_.Exception.Message)" | Out-File @OFHT
break
}
while ($job.JobState -ne 'Transferred') {
Write-Progress -activity 'Downloading WMF4.0' -Status 'Percent completed: ' -PercentComplete (
$job.BytesTransferred*100/$job.BytesTotal
)
}
Switch($job.JobState) {
'Transferred' {
Start-Sleep -Seconds 1
Complete-BitsTransfer -BitsJob $job -ErrorAction SilentlyContinue
break
}
'Error' {
$job | Format-List
break
}
default {}
}
if ((Get-AuthenticodeSignature -FilePath $MSU -ErrorAction SilentlyContinue).Status.value__ -ne 0) {
Write-Warning 'Signature from WMF4.0 file downloaded is not valid'
"$((get-date).ToString('s')) ; Signature from WMF4.0 file downloaded is not valid" | Out-File @OFHT
break
}
# SHA256 FBC0889528656A3BC096F27434249F94CBA12E413142AA38946FCDD8EDF6F4C5
Write-Verbose -Message 'Launching the installation of WMF4.0'
"$((get-date).ToString('s')) ; Launching the installation of WMF4.0" | Out-File @OFHT
$SPHT.Add('FilePath',"$($env:systemroot)\system32\wusa.exe")
$SPHT.Add('ArgumentList',@($MSU,'/quiet','/norestart'))
try {
Start-Process @SPHT
} catch {
Write-Warning -Message "Failed to start the installation of WMF4.0 because $($_.Exception.Message)"
"$((get-date).ToString('s')) ; Failed to start the installation of WMF4.0 because $($_.Exception.Message)" |
Out-File @OFHT
}
} else {
Write-Warning -Message 'WMF4.0 is installed and a reboot is pending'
"$((get-date).ToString('s')) ; WMF4.0 is installed and a reboot is pending" | Out-File @OFHT
}
} else {
# STAGE 1: Install missing .Net 4.5.2
$MSU = Join-Path -Path $($env:systemroot) -ChildPath 'temp\ndp452-kb2901983-x86-x64-enu.exe'
# Download with BITS as we are running PS 2.0 or 3.0 and don't have yet Invoke-WebRequest cmdlet
try {
$job = Start-BitsTransfer -Suspended -Asynchronous -Source $DotNetURI -Destination $MSU -ErrorAction Stop
$null = Resume-BitsTransfer -BitsJob $job -Asynchronous -ErrorAction Stop
} catch {
Write-Warning -Message "Failed to initiate BITS transfer because $($_.Exception.Message)"
"$((get-date).ToString('s')) ; Failed to initiate BITS transfer because $($_.Exception.Message)" | Out-File @OFHT
break
}
while ($job.JobState -ne 'Transferred') {
Write-Progress -activity 'Downloading .Net 4.5.2' -Status 'Percent completed: ' -PercentComplete (
$job.BytesTransferred*100/$job.BytesTotal
)
}
Switch($job.JobState) {
'Transferred' {
Start-Sleep -Seconds 1
Complete-BitsTransfer -BitsJob $job -ErrorAction SilentlyContinue
break
}
'Error' {
$job | Format-List
break
}
default {}
}
if ((Get-AuthenticodeSignature -FilePath $MSU -ErrorAction SilentlyContinue).Status.value__ -ne 0) {
Write-Warning 'Signature from file downloaded .Net 4.5.2 is not valid'
"$((get-date).ToString('s')) ; Signature from file downloaded .Net 4.5.2 is not valid" | Out-File @OFHT
break
}
# SHA256 6C2C589132E830A185C5F40F82042BEE3022E721A216680BD9B3995BA86F3781
Write-Verbose -Message 'Launching a silent installation of .Net 4.5.2 and waiting for it to complete'
"$((get-date).ToString('s')) ; Launching the installation of .Net 4.5.2 " | Out-File @OFHT
$SPHT.Add('FilePath',$MSU)
$SPHT.Add('ArgumentList',@('/q','/norestart','/log',"$($env:SystemRoot)\TEMP\NetFx.4.5.2.log"))
try {
Start-Process @SPHT
} catch {
Write-Warning -Message "Failed to start the installation of .Net 4.5.2 because $($_.Exception.Message)"
"$((get-date).ToString('s')) ; Failed to start the installation of .Net 4.5.2 because $($_.Exception.Message)" |
Out-File @OFHT
}
}
}
}
End {}
view raw wmf2towmf5.ps1 hosted with ❤ by GitHub

No doubt that PowerShell rocks and that it’s a key component to immutable infrastructure 😎

7 thoughts on “Deploy WMF5.0 on Windows 7

  1. Pingback: Dew Drop – May 18, 2016 (#2254) | Morning Dew

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.