Functions/Update-WindowsImageWMF.ps1
<#
.Synopsis Updates WMF to 4.0, 5.0 Production Preview or 5.0 (and .NET to 4.6) in a Windows Update Image .DESCRIPTION This Command downloads WMF 4.0, 5.0PP or 5.0 (Production Preview) and .NET 4.6 offline installer Creates a temp VM and updates .NET if needed and WMF .EXAMPLE Update-UpdateImageWMF -Path C:\WITExample Updates every Image in c:\WITExample\BaseImages .EXAMPLE Update-UpdateImageWMF -Path C:\WitExample -Name Server2012R2Core Updates only C:\WitExample\BaseImages\Server2012R2Core_Base.vhdx #> function Update-WindowsImageWMF { [CmdletBinding(SupportsShouldProcess = $true)] #[OutputType([String])] Param ( # Path to the Windows Image Tools Update Folders (created via New-WindowsImageToolsExample) [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [ValidateScript({ if (Test-Path $_) { $true } else { throw "Path $_ does not exist" } })] [Alias('FullName')] $Path, # Name of the Image to update [Parameter(Mandatory = $true)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [Alias('FriendlyName')] [string[]] $ImageName, # Use WMF 4 instead of the default WMF 5 [switch] $Wmf4, # Use WMF5 Production Preview instead of the default WMF 5 (overrides -vmf4) [switch] $Wmf5pp ) foreach ($image in $ImageName) { $parentVHD = "$Path\BaseImage\$($image)_Base.vhdx" $target = "$Path\BaseImage\$($image)_Update.vhdx" if ($pscmdlet.ShouldProcess("$parentVHD", 'Update WMF in Windows Image Tools Update Image')) { $ParametersToPass = @{} foreach ($key in ('Whatif', 'Verbose', 'Debug')) { if ($PSBoundParameters.ContainsKey($key)) { $ParametersToPass[$key] = $PSBoundParameters[$key] } } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : Creating $target from $parentVHD" $null = New-VHD -Path $target -ParentPath $parentVHD #region Validate Input try { $null = Test-Path -Path "$Path\BaseImage" -ErrorAction Stop $null = Test-Path -Path "$Path\Resource" -ErrorAction Stop } catch { Throw "$Path missing required folder structure use New-WindowsImagetoolsExample to create example" } if (-not(Test-Path -Path "$Path\BaseImage\$($ImageName)_Base.vhdx")) { Throw "BaseImage for $ImageName does not exists. Use Add-UpdateImage first" } #endregion #region Update Resource Folder ## download WMF $wmfPath = "$Path\Resource\WMF\5" $wmfDownloadUrl = 'http://aka.ms/wmf5latest' if ($Wmf4) { $wmfPath = "$Path\Resource\WMF\4" $wmfDownloadUrl = 'http://www.microsoft.com/en-us/download/details.aspx?id=40855' } if ($Wmf5pp) { $wmfPath = "$Path\Resource\WMF\5pp" $wmfDownloadUrl = 'https://www.microsoft.com/en-us/download/details.aspx?id=48729' } try { if (-not (Test-Path -Path $wmfPath)) { $nul = mkdir -Path $wmfPath } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : Checking for the latest WMF in $wmfPath" $confirmationPage = 'http://www.microsoft.com/en-us/download/' + $((Invoke-WebRequest -Uri $wmfDownloadUrl -UseBasicParsing).links | Where-Object -Property Class -EQ -Value 'mscom-link download-button dl' | ForEach-Object -MemberName href) $directURLs = (Invoke-WebRequest -Uri $confirmationPage -UseBasicParsing).Links | Where-Object -Property Class -EQ -Value 'mscom-link' | Where-Object -Property href -Like -Value '*.msu' | ForEach-Object -MemberName href foreach ($directURL in $directURLs) { $filename = $directURL -split '/' | Select-Object -Last 1 if (-not (Test-Path -Path "$wmfPath\$filename" )) { Write-Warning -Message "[$($MyInvocation.MyCommand)] : Checking for the latest WMF : $filename Missing, Downloading" $download = Invoke-WebRequest -Uri $directURL -OutFile "$wmfPath\$filename" } else { Write-Verbose -Message "[$($MyInvocation.MyCommand)] : Checking for the latest WMF : $wmfPath\$filename : Found" } } } catch { if (-not (Test-Path -Path "$wmfPath\*.msu")) { throw "Unable to downlaod WMF to $wmfPath. please download WMF manualy and place in $wmfPath " } } ## download .NET 4.6 try { if (-not (Test-Path -Path $Path\Resource\dotNET)) { mkdir -Path $Path\Resource\dotNET } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : Checking for .NET 4.6" $directURL = 'https://download.microsoft.com/download/C/3/A/C3A5200B-D33C-47E9-9D70-2F7C65DAAD94/NDP46-KB3045557-x86-x64-AllOS-ENU.exe' $filename = 'dotNet4-6.exe' if (-not (Test-Path -Path "$Path\Resource\dotNET\$filename" )) { Write-Warning -Message "[$($MyInvocation.MyCommand)] : Checking for .NET 4.6 : Missing : Downloading" $download = Invoke-WebRequest -Uri $directURL -OutFile "$Path\Resource\dotNET\$filename" } } catch { if (-not (Test-Path -Path "$Path\Resource\dotNET\$filename")) { throw "Unable to downlaod .net 4.6 to $Path\Resource\dotNET\$filename. please download .net 4.6 manualy " } } #endregion #region Install .NET $dotNetInstallAtStartup = { Start-Transcript -Path $PSScriptRoot\AtStartup.log -Append $currentDotNetVersionv = (Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -Recurse | Get-ItemProperty -Name Version, Release -EA 0 | Where-Object -FilterScript { $_.PSChildName -match '^(?!S)\p{L}' } | Sort-Object -Property version -Descending | Select-Object -First 1 ).version if ($currentDotNetVersionv -lt 4.6) { if (-not (Test-Path -Path c:\PsTemp\dotNET\attempt.txt)) { Get-Date | Out-File -FilePath c:\PsTemp\dotNET\attempt.txt Write-Verbose -Message '.Net 4.6 : Installing' -Verbose Start-Process -Verb runas -Wait -FilePath 'C:\PsTemp\dotNET\dotNet4-6.exe' -ArgumentList '/q', '/norestart', '/log c:\PsTemp\dotNet\dotNetLog.htm' } else { Write-Error -Message '.Net 4.6 : install attempted but failed!' Start-Sleep -Seconds 30 # Stop-Computer does not have -force in 2008/win7 WMF2 if ((Get-Command Stop-Computer -Syntax) -like '*[force]*') { Stop-Computer -Verbose -Force } else { shutdown.exe /s /t 0 /f } Stop-Transcript } } else { Get-Date | Out-File -FilePath c:\PsTemp\dotNET\Verified.txt Write-Verbose -Message '.Net 4.6 : detected shuting down' -Verbose # Stop-Computer does not have -force in 2008/win7 WMF2 if ((Get-Command Stop-Computer -Syntax) -like '*[force]*') { Stop-Computer -Verbose -Force } else { shutdown.exe /s /t 0 /f } Stop-Transcript } Start-Sleep -Seconds 30 Write-Verbose -Message 'Rebooting computer' -Verbose # Restart-Computer does not have -force in 2008/win7 WMF2 if ((Get-Command Restart-Computer -Syntax) -like '*[force]*') { Restart-Computer -Verbose -Force } else { shutdown.exe /r /t 0 /f } Stop-Transcript } $AddDotNetFilesBlock = { if (-not (Test-Path -Path "$($driveLetter):\PsTemp")) { $null = mkdir -Path "$($driveLetter):\PsTemp" } if (-not (Test-Path -Path "$($driveLetter):\PsTemp\dotNET")) { $null = mkdir -Path "$($driveLetter):\PsTemp\dotNET" } $null = New-Item -Path "$($driveLetter):\PsTemp" -Name AtStartup.ps1 -ItemType 'file' -Value $dotNetInstallAtStartup -Force $null = Copy-Item -Path "$Path\Resource\dotNET\$filename" -Destination "$($driveLetter):\PsTemp\dotNET\$filname" } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : .NET : Adding installer to $target" Write-Verbose -Message "[$($MyInvocation.MyCommand)] : .NET : updateting AtStartup script" MountVHDandRunBlock -vhd $target -block $AddDotNetFilesBlock $vmGeneration = 1 if ((GetVHDPartitionStyle -vhd $target) -eq 'GPT') { $vmGeneration = 2 } $ConfigData = Get-UpdateConfig -Path $Path Write-Verbose -Message "[$($MyInvocation.MyCommand)] : .NET : Creating temp vm and waiting " createRunAndWaitVM -vhdPath $target -vmGeneration $vmGeneration -configData $ConfigData @ParametersToPass #endregion #region Install WMF $verifyWmfVersion4 = { Start-Transcript -Path $PSScriptRoot\AtStartup.log -Append if ($PSVersionTable.PSVersion.Major -ge 4) { Write-Verbose -Message 'WMF : version varified' Get-Date | Out-File -FilePath c:\PsTemp\ChangesMade.txt } else { Write-Warning -Message "WMF : Excpected version 4, found $($PSVersionTable.PSVersion.Major)" } Stop-Transcript Stop-Computer -Force } $verifyWmfVersion5 = { Start-Transcript -Path $PSScriptRoot\AtStartup.log -Append if ($PSVersionTable.PSVersion.Major -ge 5) { Write-Verbose -Message 'WMF : version varified' Get-Date | Out-File -FilePath c:\PsTemp\ChangesMade.txt } else { Write-Warning -Message "WMF : Excpected version 4, found $($PSVersionTable.PSVersion.Major)" } Stop-Transcript Stop-Computer -Force } if ($Wmf4) { $VeirfyWmfAtStartup = $verifyWmfVersion4 } else { $VeirfyWmfAtStartup = $verifyWmfVersion5 } $addWmfFilesBlock = { foreach ($update in (Get-ChildItem -Path $wmfPath\*.msu ).FullName ) { Write-Verbose -Message "checking if $update applies to $target" $null = Add-WindowsPackage -PackagePath $update -Path "$($driveLetter):" } $null = New-Item -Path "$($driveLetter):\PsTemp" -Name AtStartup.ps1 -ItemType 'file' -Value $VeirfyWmfAtStartup -Force } Write-Verbose -Message "[$($MyInvocation.MyCommand)] : WMF : Applying WMF to $target and Updating AtStartup script" MountVHDandRunBlock -vhd $target -block $addWmfFilesBlock Write-Verbose -Message "[$($MyInvocation.MyCommand)] : WMF : creating temp VM to finalize install on $target" createRunAndWaitVM -vhdPath $target -vmGeneration $vmGeneration -configData $ConfigData @ParametersToPass #endregion #region check for changes and merge or delete Write-Verbose -Message "[$($MyInvocation.MyCommand)] : WMF : Checking if changes made" $checkresultsBlock = { Test-Path -Path "$($driveLetter):\PsTemp\ChangesMade.txt" if (Test-Path -Path "$($driveLetter):\PsTemp\ChangesMade.txt") { Remove-Item -Path "$($driveLetter):\PsTemp\AtStartup.ps1" -ErrorAction SilentlyContinue } } $ChangesMade = MountVHDandRunBlock -vhd $target -block $checkresultsBlock if ($ChangesMade) { Write-Verbose -Message "[$($MyInvocation.MyCommand)] : WMF : Changes found : Merging $target into $parentVHD" Merge-VHD -Path $target -DestinationPath $parentVHD } else { Write-Verbose -Message "[$($MyInvocation.MyCommand)] : WMF : No Changes : Discarding $target" Remove-Item $target } #endregion } } } |