Scripts/Install-OSDUpdatePackage.ps1
#Requires -Version 5 <# .SYNOPSIS Installs Updates in an OSDUpdate Package for Office and Windows .DESCRIPTION Installs Updates in an OSDUpdate Package for Office and Windows .NOTES Author: David Segura Website: osdeploy.com Twitter: @SeguraOSD Version: 21.1.7.2 #> function Convert-GuidToCompressedGuid { <# .SYNOPSIS This converts a GUID to a compressed GUID also known as a product code. .DESCRIPTION This function will typically be used to figure out the product code that matches up with the product code stored in the 'SOFTWARE\Classes\Installer\Products' registry path to a MSI installer GUID. .EXAMPLE Convert-GuidToCompressedGuid -Guid '{7C6F0282-3DCD-4A80-95AC-BB298E821C44}' This example would output the compressed GUID '2820F6C7DCD308A459CABB92E828C144' .PARAMETER Guid The GUID you'd like to convert. .LINK https://www.adamtheautomator.com/compressed-guid-with-powershell/ #> [CmdletBinding()] [OutputType()] param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)] [string]$Guid ) begin { $Guid = $Guid.Replace('-', '').Replace('{', '').Replace('}', '') } process { try { $Groups = @( $Guid.Substring(0, 8).ToCharArray(), $Guid.Substring(8, 4).ToCharArray(), $Guid.Substring(12, 4).ToCharArray(), $Guid.Substring(16, 16).ToCharArray() ) $Groups[0..2] | foreach { [array]::Reverse($_) } $CompressedGuid = ($Groups[0..2] | foreach { $_ -join '' }) -join '' $chararr = $Groups[3] for ($i = 0; $i -lt $chararr.count; $i++) { if (($i % 2) -eq 0) { $CompressedGuid += ($chararr[$i+1] + $chararr[$i]) -join '' } } $CompressedGuid } catch { Write-Error $_.Exception.Message } } } function Convert-CompressedGuidToGuid { <# .SYNOPSIS This converts a compressed GUID also known as a product code into a GUID. .DESCRIPTION This function will typically be used to figure out the MSI installer GUID that matches up with the product code stored in the 'SOFTWARE\Classes\Installer\Products' registry path. .EXAMPLE Convert-CompressedGuidToGuid -CompressedGuid '2820F6C7DCD308A459CABB92E828C144' This example would output the GUID '{7C6F0282-3DCD-4A80-95AC-BB298E821C44}' .PARAMETER CompressedGuid The compressed GUID you'd like to convert. .LINK https://www.adamtheautomator.com/convert-compressed-guid-to-guid/ #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)] [ValidatePattern('^[0-9a-fA-F]{32}$')] [string]$CompressedGuid ) process { $Indexes = [ordered]@{ 0 = 8; 8 = 4; 12 = 4; 16 = 2; 18 = 2; 20 = 2; 22 = 2; 24 = 2; 26 = 2; 28 = 2; 30 = 2 } #$Guid = '{' $Guid = '' foreach ($index in $Indexes.GetEnumerator()) { $part = $CompressedGuid.Substring($index.Key, $index.Value).ToCharArray() [array]::Reverse($part) $Guid += $part -join '' } $Guid = $Guid.Insert(9,'-').Insert(14, '-').Insert(19, '-').Insert(24, '-') #$Guid + '}' $Guid + '' } } #====================================================================================== # Validate Admin Rights #====================================================================================== Write-Host "" # Verify Running as Admin $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") If (!( $isAdmin )) { Write-Host "Checking User Account Control settings ..." -ForegroundColor Green if ((Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).EnableLUA -eq 0) { #UAC Disabled Write-Host '========================================================================================' -ForegroundColor DarkGray Write-Host "User Account Control is Disabled ... " -ForegroundColor Green Write-Host "You will need to correct your UAC Settings ..." -ForegroundColor Green Write-Host "Try running this script in an Elevated PowerShell session ... Exiting" -ForegroundColor Green Write-Host '========================================================================================' -ForegroundColor DarkGray Start-Sleep -s 10 Exit 0 } else { #UAC Enabled Write-Host "UAC is Enabled" -ForegroundColor Green Start-Sleep -s 3 if ($Silent) { Write-Host "-- Restarting as Administrator (Silent)" -ForegroundColor Cyan ; Start-Sleep -Seconds 1 Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -Silent" -Verb RunAs -Wait } elseif($Restart) { Write-Host "-- Restarting as Administrator (Restart)" -ForegroundColor Cyan ; Start-Sleep -Seconds 1 Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -Restart" -Verb RunAs -Wait } else { Write-Host "-- Restarting as Administrator" -ForegroundColor Cyan ; Start-Sleep -Seconds 1 Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs -Wait } Exit 0 } } else { Write-Host '========================================================================================' -ForegroundColor DarkGray Write-Host "-- Running with Elevated Permissions ..." -ForegroundColor Cyan ; Start-Sleep -Seconds 1 Write-Host '========================================================================================' -ForegroundColor DarkGray } #====================================================================================== # Script Information #====================================================================================== $Invocation = (Get-Variable MyInvocation -Scope Script).Value $ScriptPath = Split-Path -Parent $Invocation.MyCommand.Path $ParentName = Split-Path $ScriptPath -Leaf #====================================================================================== # Logs #====================================================================================== $OSDAppName = "OSDUpdate-$ParentName" $OSDLogs = "$env:Temp" if (!(Test-Path $OSDLogs)) {New-Item $OSDLogs -ItemType Directory -Force | Out-Null} $OSDLogName = "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-$OSDAppName.log" Start-Transcript -Path (Join-Path $OSDLogs $OSDLogName) #====================================================================================== # Start Script #====================================================================================== Write-Host "Start ... $(Join-Path $PSScriptRoot $MyInvocation.MyCommand.Name)" -ForegroundColor Green Write-Host "" #====================================================================================== # OS Information #====================================================================================== $OSCaption = $((Get-WmiObject -Class Win32_OperatingSystem).Caption).Trim() $OSArchitecture = $((Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture).Trim() $OSProductType = $((Get-WmiObject -Class Win32_OperatingSystem).ProductType) $OSVersion = $((Get-WmiObject -Class Win32_OperatingSystem).Version).Trim() $OSBuildNumber = $((Get-WmiObject -Class Win32_OperatingSystem).BuildNumber).Trim() Write-Host "Operating System: $OSCaption" -ForegroundColor Cyan Write-Host "OS Architecture: $OSArchitecture" -ForegroundColor Cyan Write-Host "OS ProductType: $OSProductType" -ForegroundColor Cyan Write-Host "OS Version: $OSVersion" -ForegroundColor Cyan Write-Host "OS Build Number: $OSBuildNumber" -ForegroundColor Cyan if ($OSVersion -Like "10*") { $OSReleaseID = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId if ($OSReleaseID -ge 2009) { $OSReleaseID = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name DisplayVersion).DisplayVersion } Write-Host "OS Release ID: $OSReleaseID" -ForegroundColor Cyan } #====================================================================================== # Begin #====================================================================================== Write-Host "Install-OSDUpdate Package" -ForegroundColor Green #====================================================================================== # Get OSDUpdate Package Catalog #====================================================================================== $Updates = @() $UpdateCatalogs = Get-ChildItem $PSScriptRoot "OSDUpdatePackage.xml" Try { foreach ($Catalog in $UpdateCatalogs) { $Updates += Import-Clixml -Path $Catalog.FullName } } Catch { Write-Warning -Message "Could not locate OSDUpdatePackage.xml ... Exiting" Break } #====================================================================================== # Determine Catalog #====================================================================================== $Catalog = ($Updates | Select-Object -First 1).Catalog Write-Host "OSDUpdate Catalog: $Catalog" -ForegroundColor Cyan #====================================================================================== # Catalog Office #====================================================================================== if ($Catalog -like "Office*") { #====================================================================================== # Installed Patches #====================================================================================== $PatchesInstalledRegistry = @() $PatchesInstalledRegistry = 'HKLM:\SOFTWARE\Classes\Installer\Patches' $PatchesInstalledProductCode = @() $PatchesInstalledProductCode = Get-ChildItem -Path $PatchesInstalledRegistry -EA SilentlyContinue | Select-Object -Property @{Name="ProductCode"; Expression = {$_.PSChildName}} -Unique $PatchesInstalledGuids = @() foreach ($InstalledPatch in $PatchesInstalledProductCode) { $InstalledPatchGuid = Convert-CompressedGuidToGuid -CompressedGuid "$($InstalledPatch.ProductCode)" $PatchesInstalledGuids += $InstalledPatchGuid } #====================================================================================== # Available Patches (MSP's) #====================================================================================== $PatchesAvailable = @() $PatchesAvailable = Get-ChildItem "$ScriptPath" -Recurse -File -Include *.msp | Select-Object -Property LastWriteTime,Name,Length,FullName,Directory,BaseName,Extension $PatchesAvailable = $PatchesAvailable | Sort-Object -Property @{Expression = {$_.LastWriteTime}; Ascending = $true}, Length -Descending #====================================================================================== # Get Patch XML Information #====================================================================================== foreach ($Patch in $PatchesAvailable) { $PatchXml = "$($Patch.Directory)\$($Patch.BaseName).xml" $Patch | Add-Member -MemberType NoteProperty -Name PatchGuid -value '' $Patch | Add-Member -MemberType NoteProperty -Name ProductCode -value '' $Patch | Add-Member -MemberType NoteProperty -Name TargetProductCode -value '' if (Test-Path $PatchXml) { $xml = [xml](Get-Content $PatchXml) $Patch.PatchGuid = $($xml.MsiPatch | Select PatchGuid).PatchGuid $Patch.ProductCode = Convert-GuidToCompressedGuid -Guid $($Patch.PatchGuid) $Patch.TargetProductCode = $($xml.MsiPatch.TargetProductCode) } } #====================================================================================== # Set InstallationStatus #====================================================================================== foreach ($Patch in $PatchesAvailable) { $Patch | Add-Member -MemberType NoteProperty -Name InstallStatus -value '' foreach ($PatchInstalled in $PatchesInstalledProductCode) { if ($Patch.ProductCode -eq $PatchInstalled.ProductCode) { $Patch.InstallStatus = 'Installed' } } } #====================================================================================== # Install Updates #====================================================================================== foreach ($Patch in $PatchesAvailable) { $PatchName = $($Patch.Directory) | Split-Path -Leaf if ($Patch.InstallStatus -eq 'Installed') { Write-Host "Installed: $PatchName $($Patch.Name)" -ForegroundColor DarkGray } else { Write-Host "$PatchName $($Patch.Name)" -ForegroundColor Cyan msiexec /p "$($Patch.FullName)" /qn REBOOT=ReallySuppress MSIRESTARTMANAGERCONTROL=Disable | Out-Null } } } #====================================================================================== # Catalog Windows #====================================================================================== if ($Catalog -like "Windows*") { #====================================================================================== # Sessions #====================================================================================== [xml]$SessionsXML = Get-Content -Path "$env:WinDir\Servicing\Sessions\Sessions.xml" $Sessions = $SessionsXML.SelectNodes('Sessions/Session') | ForEach-Object { New-Object -Type PSObject -Property @{ Id = $_.Tasks.Phase.package.id KBNumber = $_.Tasks.Phase.package.name TargetState = $_.Tasks.Phase.package.targetState Client = $_.Client Complete = $_.Complete Status = $_.Status } } $Sessions = $Sessions | Where-Object {$_.Id -like "Package*"} $Sessions = $Sessions | Select-Object -Property Id, KBNumber, TargetState, Client, Status, Complete | Sort-Object Complete -Descending #====================================================================================== # Sort Updates #====================================================================================== $Updates = $Updates | Sort-Object -Property CreationDate #====================================================================================== # Architecture #====================================================================================== if ($OSArchitecture -like "*64*") {$Updates = $Updates | Where-Object {$_.UpdateArch -eq 'x64'}} else {$Updates = $Updates | Where-Object {$_.UpdateArch -eq 'x86'}} #====================================================================================== # OSReleaseID #====================================================================================== if ($OSProductType -eq 1) { $Updates = $Updates | Where-Object {$_.Catalog -notlike "*Server*"} if ($OSVersion -like "6.1") {$Updates = $Updates | Where-Object {$_.Catalog -like "Windows 7*"}} } else { $Updates = $Updates | Where-Object {$_.Catalog -like "*Server*"} } if ($OSVersion -like "10.*") { $Updates = $Updates | Where-Object {$_.UpdateBuild -eq $OSReleaseID} if ($OSCaption -match '11') { $Updates = $Updates | Where-Object {$_.UpdateOS -eq 'Windows 11'} } else { $Updates = $Updates | Where-Object {$_.UpdateOS -ne 'Windows 11'} } } #====================================================================================== # Get-Hotfix #====================================================================================== $InstalledUpdates = Get-HotFix #====================================================================================== # Windows Updates #====================================================================================== Write-Host "Updating Windows" -ForegroundColor Green foreach ($Update in $Updates) { if ($Update.UpdateGroup -eq 'SSU') { $UpdatePath = "$PSScriptRoot\$($Update.Title)\$($Update.FileName)" if (Test-Path "$UpdatePath") { Write-Host "$UpdatePath" -ForegroundColor DarkGray if ($InstalledUpdates | Where-Object HotFixID -like "*$($Update.KBNumber)") { Write-Host "KB$($Update.KBNumber) is already installed" -ForegroundColor Cyan } else { Write-Host "Installing $($Update.Title) ..." -ForegroundColor Cyan Dism /Online /Add-Package /PackagePath:"$UpdatePath" /NoRestart } } else { Write-Warning "Not Found: $UpdatePath" } } } foreach ($Update in $Updates) { if ($Update.UpdateGroup -ne 'SSU') { $UpdatePath = "$PSScriptRoot\$($Update.Title)\$($Update.FileName)" if (Test-Path "$UpdatePath") { Write-Host "$UpdatePath" -ForegroundColor DarkGray if ($InstalledUpdates | Where-Object HotFixID -like "*$($Update.KBNumber)") { Write-Host "KB$($Update.KBNumber) is already installed" -ForegroundColor Cyan } else { Write-Host "Installing $($Update.Title) ..." -ForegroundColor Cyan Dism /Online /Add-Package /PackagePath:"$UpdatePath" /NoRestart } } else { #Write-Warning "Not Found: $UpdatePath" } } } } #====================================================================================== # Complete #====================================================================================== Write-Host "" Write-Host "Complete ... $(Join-Path $PSScriptRoot $MyInvocation.MyCommand.Name)" -ForegroundColor Green Stop-Transcript Start-Sleep 5 |