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
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:
- fullfil the .Net 4.x requirement
- update to WMF 4.0
- reboot
- update to WMF 5.0
- 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:
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
- Re-run it and it will kindly tell you that a reboot is pending
- Re-run it after the 1st restart, it will move on and install WMF 5.0
- Re-run it after the 2nd restart and it will do nothing except telling you that WMF 5.0 is already installed
Re-run it and it moves to the next step and installs WMF 4.0
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 {} |
No doubt that PowerShell rocks and that it’s a key component to immutable infrastructure π
Pingback: Dew Drop – May 18, 2016 (#2254) | Morning Dew
Thank you! This is exactly what my company needed to upgrade our Windows 7 systems to Powershell 5!
Reblogged this on and commented:
This really saved my day. Thanks for sharing this Emin!
Thx for the feedback. I’m glad it worked for you π
Does this work for Windows 10 also?
Nevermind, it ships with 5.0 default π
Can you update the script so it adds 5.1?
See https://blogs.msdn.microsoft.com/powershell/2017/01/19/windows-management-framework-wmf-5-1-released/
If you’ve already installed WMF5.0 using the above script. You can use the Microsoft Update Catalog to push 5.1 using your prefered update solution/product.
Or you can use the zip file provided on the download center and the script provided in the zip:


https://www.microsoft.com/en-us/download/details.aspx?id=54616
Make sure to read these as well before doing anything.
https://docs.microsoft.com/en-us/powershell/wmf/5.1/release-notes
https://docs.microsoft.com/en-us/powershell/wmf/5.1/install-configure
PS: I’m still using the above script in my environment because it deploys the .net requirement and powershell 4.0 that is a specific requirement in my environment.