Public/HP.Client.Update/Public/Install-HPUpdate.ps1
|
function Install-HPUpdate { <# .SYNOPSIS Downloads, validates, and installs applicable HP SoftPaq updates. .EXAMPLE Get-HPUpdate | Install-HPUpdate .EXAMPLE Install-HPUpdate -PackageIds sp173903 -Force #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Search', ConfirmImpact = 'Medium')] param( [Parameter(Position = 0, ParameterSetName = 'Search')] [Alias('PackageId', 'SoftpaqId')][string[]]$PackageIds, [Parameter(Position = 0, ValueFromPipeline, ParameterSetName = 'Packages')] [Alias('Package', 'Update')][psobject[]]$Packages, [string[]]$ExcludePackageIds, [string[]]$Category, [string[]]$Type, [string[]]$ReleaseType, [switch]$Force, [switch]$NoLog, [uri]$Proxy, [pscredential]$ProxyCredential, [switch]$ProxyUseDefaultCredentials, [string]$Path ) begin { $pipelinePackages = [System.Collections.Generic.List[object]]::new() $historyRecords = [System.Collections.Generic.List[object]]::new() $results = [System.Collections.Generic.List[object]]::new() } process { foreach ($package in @($Packages)) { if ($null -ne $package) { $pipelinePackages.Add($package) } } } end { function Expand-HPUpdatePackageIdList { param([string[]]$Values) @($Values | ForEach-Object { $_ -split ',' } | ForEach-Object { $_.Trim() } | Where-Object { $_ }) } function Write-HPUpdateInstallationLog { param([string]$Message) if ($NoLog -or -not $logPath) { return } Add-Content -LiteralPath $logPath -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $Message" -Encoding UTF8 } function Set-HPUpdateInstallResultDisplay { param([Parameter(Mandatory)][psobject]$Result) $Result.PSObject.TypeNames.Insert(0, 'HP.Client.Update.HPInstallResult') $properties = [string[]]@('ID', 'Title', 'Success', 'RebootRequired', 'PendingAction', 'ExitCode', 'FailureReason', 'LogPath', 'Runtime') $propertySet = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet', $properties) $members = [System.Management.Automation.PSMemberSet]::new('PSStandardMembers', [System.Management.Automation.PSMemberInfo[]]@($propertySet)) $Result.PSObject.Members.Add($members) } function New-HPUpdateInstallResult { param([psobject]$Package, [bool]$Success, [bool]$RebootRequired, $ExitCode, [string]$FailureReason, [timespan]$Runtime) $result = [pscustomobject]@{ ID = $Package.ID; SoftpaqID = $Package.SoftpaqID; Title = $Package.Title; Success = $Success; RebootRequired = $RebootRequired; PendingAction = if ($RebootRequired) { 'REBOOT_MANDATORY' } else { 'NONE' }; ExitCode = $ExitCode; FailureReason = $FailureReason; LogPath = $logPath; Runtime = $Runtime } Set-HPUpdateInstallResultDisplay -Result $result $historyRecords.Add((New-HPUpdateHistoryRecord -Package $Package -Result $result)) $results.Add($result) $result } $requestedIds = @(Expand-HPUpdatePackageIdList -Values $PackageIds) $excludedIds = @(Expand-HPUpdatePackageIdList -Values $ExcludePackageIds) $selectedPackages = if ($PSCmdlet.ParameterSetName -eq 'Packages') { @($pipelinePackages) } elseif ($requestedIds.Count) { @(Get-HPUpdate -All | Where-Object { $_.ID -in $requestedIds -or $_.SoftpaqID -in $requestedIds -or $_.PackageID -in $requestedIds }) } else { @(Get-HPUpdate) } if ($excludedIds.Count) { $selectedPackages = @($selectedPackages | Where-Object { $_.ID -notin $excludedIds -and $_.SoftpaqID -notin $excludedIds -and $_.PackageID -notin $excludedIds }) } if ($PSBoundParameters.ContainsKey('Category')) { $selectedPackages = @($selectedPackages | Where-Object { $_.Category -in $Category }) } if ($PSBoundParameters.ContainsKey('Type')) { $selectedPackages = @($selectedPackages | Where-Object { $_.Type -in $Type }) } if ($PSBoundParameters.ContainsKey('ReleaseType')) { $selectedPackages = @($selectedPackages | Where-Object { $_.ReleaseType -in $ReleaseType }) } $selectedPackages = @($selectedPackages | Sort-Object ID -Unique) if (-not $selectedPackages.Count) { Write-Verbose 'No applicable HP updates matched the specified criteria.'; return } foreach ($package in $selectedPackages) { if ($package.PSObject.TypeNames -notcontains 'HP.Client.Update.HPUpdate') { throw 'Install-HPUpdate requires update objects returned by Get-HPUpdate.' } if ($package.IsApplicable -ne $true -or $package.IsInstalled -ne $false) { throw "Refusing to install '$($package.Title)': it is not positively marked applicable and not installed." } if (-not $package.DownloadUri -or -not $package.Sha256 -or -not $package.Installer.Unattended) { throw "HP catalog metadata is incomplete for '$($package.Title)'." } } if (-not $Force -and -not $WhatIfPreference) { Write-Host 'The following HP updates will be installed:' $selectedPackages | Select-Object SoftpaqID, Name, Version, Category | Format-Table -AutoSize | Out-Host if (-not $PSCmdlet.ShouldContinue("Install $($selectedPackages.Count) HP update package(s)?", 'Install HP updates')) { return } } $isAdministrator = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $WhatIfPreference -and -not $isAdministrator) { throw 'Install-HPUpdate must be run from an elevated PowerShell session.' } $payloadDirectory = if ([string]::IsNullOrWhiteSpace($Path)) { Get-HPUpdatePath -Name Downloads } else { $Path } $logPath = $null if (-not $NoLog -and -not $WhatIfPreference) { $logDirectory = Get-HPUpdatePath -Name Logs -Create $logPath = Join-Path $logDirectory "$(Get-Date -Format 'yyyyMMdd_HHmmss_fff')-$([guid]::NewGuid().ToString('N')).log" Write-HPUpdateInstallationLog "Selected $($selectedPackages.Count) HP update package(s)." } try { Write-Host "Preparing $($selectedPackages.Count) HP update package(s)..." $downloadedPackages = [System.Collections.Generic.List[object]]::new() $downloadNumber = 0 foreach ($package in $selectedPackages) { $downloadNumber++ if (-not $PSCmdlet.ShouldProcess($package.Title, 'Download, verify, and install HP update')) { continue } $startedAt = Get-Date try { Write-Host "Downloading update $downloadNumber of $($selectedPackages.Count): $($package.Title)" $sourceUri = [uri]$package.DownloadUri if ($sourceUri.Scheme -ne 'https') { throw "Refusing non-HTTPS catalog payload URI: '$sourceUri'." } $null = New-Item -Path $payloadDirectory -ItemType Directory -Force $installerPath = Join-Path $payloadDirectory ([IO.Path]::GetFileName($sourceUri.AbsolutePath)) $actualDigest = if (Test-Path -LiteralPath $installerPath -PathType Leaf) { (Get-FileHash -LiteralPath $installerPath -Algorithm SHA256).Hash } if ($actualDigest -ine [string]$package.Sha256) { Write-HPUpdateInstallationLog "Downloading $($package.SoftpaqID) from $sourceUri" Invoke-HPUpdateDownload -Source $sourceUri -Destination $installerPath -Proxy $Proxy -ProxyCredential $ProxyCredential -ProxyUseDefaultCredentials:$ProxyUseDefaultCredentials -ShowProgress } else { Write-Host "Using previously downloaded payload for $($package.SoftpaqID)." } $null = Test-HPUpdatePackage -Path $installerPath -ExpectedSha256 $package.Sha256 $downloadedPackages.Add([pscustomobject]@{ Package = $package; InstallerPath = $installerPath; StartedAt = $startedAt }) } catch { Write-HPUpdateInstallationLog "Failed to download or validate $($package.SoftpaqID): $($_.Exception.Message)" $result = New-HPUpdateInstallResult -Package $package -Success $false -RebootRequired $false -ExitCode $null -FailureReason $_.Exception.Message -Runtime ((Get-Date) - $startedAt) Write-Host "Download failed: $($package.Title) - $($result.FailureReason)" $result } } if (-not $downloadedPackages.Count -and -not $results.Count) { return } if ($downloadedPackages.Count) { Write-Host 'Starting installation phase...' } $installNumber = 0 foreach ($downloadedPackage in $downloadedPackages) { $installNumber++ $package = $downloadedPackage.Package $startedAt = $downloadedPackage.StartedAt try { Write-Host "Installing Update $($package.Title) ($installNumber of $($downloadedPackages.Count))" Write-HPUpdateInstallationLog "Starting $($package.SoftpaqID): $($downloadedPackage.InstallerPath)" $process = Start-Process -FilePath $downloadedPackage.InstallerPath -ArgumentList '-s', '-e cmd.exe', "/f `"$payloadDirectory`"", '-a', "/c $($package.Installer.Arguments)" -WorkingDirectory $payloadDirectory -Wait -PassThru $successCodes = if (@($package.SuccessCodes).Count) { @($package.SuccessCodes) } else { @(0) } $success = $process.ExitCode -in $successCodes $rebootRequired = $process.ExitCode -in @($package.RebootCodes) Write-HPUpdateInstallationLog "Completed $($package.SoftpaqID) with exit code $($process.ExitCode)." New-HPUpdateInstallResult -Package $package -Success $success -RebootRequired $rebootRequired -ExitCode $process.ExitCode -FailureReason $(if ($success) { '' } else { "HP SoftPaq exited with code $($process.ExitCode)." }) -Runtime ($process.ExitTime - $process.StartTime) } catch { Write-HPUpdateInstallationLog "Failed $($package.SoftpaqID): $($_.Exception.Message)" New-HPUpdateInstallResult -Package $package -Success $false -RebootRequired $false -ExitCode $null -FailureReason $_.Exception.Message -Runtime ((Get-Date) - $startedAt) } } if ($results.Count) { $succeeded = @($results | Where-Object Success).Count $rebootCount = @($results | Where-Object RebootRequired).Count Write-Host "Installation summary: $succeeded succeeded ($rebootCount require reboot), $($results.Count - $succeeded) failed." $results | Select-Object Title, @{ Name = 'Status'; Expression = { if (-not $_.Success) { 'Failed' } elseif ($_.RebootRequired) { 'Reboot required' } else { 'Success' } } }, ExitCode, FailureReason | Format-Table -AutoSize -Wrap | Out-Host } } finally { if (-not $WhatIfPreference -and $historyRecords.Count) { try { Write-HPUpdateHistorySession -Records @($historyRecords) | Out-Null } catch { Write-Warning "Could not save HP update installation history: $($_.Exception.Message)" } } } } } |