ps/Modules/Cole.PowerShell.Developer/Scratch/apply_windows_updates.ps1

232 lines
9.2 KiB
PowerShell
Raw Normal View History

2023-05-30 22:51:22 -07:00
<#
.SYNOPSIS
Find and apply Windows Updates on remote computers
.PARAMETER ComputerNames
One or more fully qualified computer names as a string array
.PARAMETER doRestarts
Restart when completed
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerNames,
[switch]$doUpdates,
[switch]$doRestarts
)
<#
Determine which packages need to be upgraded per server for the individual AWS items
$bucketResult = (Invoke-RestMethod -Method GET -Uri https://s3.amazonaws.com/ec2-windows-drivers-downloads)
$types = @('NVMe','ENA','AWSPV')
$results = @()
foreach ($key in $bucketResult.ListBucketResult.Contents.Key) {
foreach ($type in $types) {
if ($key.StartsWith($type)) {
$results += @{ Type = ($key -split '/')[0]; Version = ($key -split '/')[1] }
}
}
}
$versionTable = @{}
foreach ($type in $types) {
$version = $results.Where({$_.Version -match '[0-9]+\.[0-9]+\.[0-9]+'}).Where({$_.Type -eq $type}).Version | Sort-Object -Descending | Select-Object -First 1
Write-Host $version
Write-Host $type
$versionTable.$type = $version
}
$JobNames = @{
NVMe = 'AWSNVMe'
ENA = 'AwsEnaNetworkDriver'
AWSPV = 'AWSPVDriver'
}
$x = Get-EC2WindowsDriverVersions
$toUpdate = $x.Where({$_.Version -ne $versionTable[$_.Type]})
$instances = (Get-CachedInstances -ProfileName temp-prod).Where({$_.Hostname -match '^tea'})
foreach ($instance in $instances) {
foreach ($update in $toUpdate) {
if ($instance.Hostname -eq "$($update.ComputerName).fh.local") {
Write-Host "updating"
Invoke-AWSConfigureAWSPackage -JobName $JobNames[$update.Type] -InstanceId $instance.InstanceId -Comment 'SRE-17714' -ProfileName 'temp-prod' -Region $instance.Region
}
}
}
#>
Write-Host "Updating chrome on all remote machines"
$sbChrome = {
choco upgrade GoogleChrome -y -r
}
Invoke-Command -ComputerName $ComputerNames -ScriptBlock $sbChrome
#region magic session things for PowerShell
# The COMObjects can not be created if you don't do this first
# I should probably have them get removed after the run, but meh
Write-Host "Ensuring VirtualAccount PSSessionConfiguration exists locally so we can run updates remotely"
if ($null -eq (Get-PSSessionConfiguration -Name 'VirtualAccount' -ErrorAction SilentlyContinue)) {
New-PSSessionConfigurationFile -RunAsVirtualAccount -Path .\VirtualAccount.pssc
# Note this will restart the WinRM service:
Register-PSSessionConfiguration -Name 'VirtualAccount' -Path .\VirtualAccount.pssc -Force
}
$ensureVirtualAccountSessionManagementExistsScriptBlock = {
Write-Host "Ensuring VirtualAccount PSSessionConfiguration exists so we can run updates remotely on $($env:COMPUTERNAME)"
if ($null -eq (Get-PSSessionConfiguration -Name 'VirtualAccount' -ErrorAction SilentlyContinue)) {
New-PSSessionConfigurationFile -RunAsVirtualAccount -Path .\VirtualAccount.pssc
# Note this will restart the WinRM service:
Register-PSSessionConfiguration -Name 'VirtualAccount' -Path .\VirtualAccount.pssc -Force
}
}
Invoke-Command -ComputerName $ComputerNames -ScriptBlock $ensureVirtualAccountSessionManagementExistsScriptBlock
#endregion magic session things for PowerShell
# Take inventory, find out if we need to reboot before we continue
$rebootRequiredScriptBlock = {
param (
[string]$computerName
)
$session = New-PSSession -ComputerName $computerName -ConfigurationName 'VirtualAccount'
$serverScript = {
$UpdateCollection = New-Object -ComObject 'Microsoft.Update.UpdateColl' -Strict
$Searcher = New-Object -ComObject 'Microsoft.Update.Searcher' -Strict
$Session = New-Object -ComObject 'Microsoft.Update.Session' -Strict
$Installer = New-Object -ComObject 'Microsoft.Update.Installer' -Strict
$Searcher.Search("") | Out-Null
$totalHistoryCount = $Searcher.GetTotalHistoryCount()
$returnUpdates = @()
$Updates = @($Searcher.Search("IsHidden=0 and IsInstalled=0").Updates)
foreach ($update in $Updates) {
$UpdateCollection.Add($update) | Out-Null
$returnUpdates += New-Object -Type PSObject -Property @{
BundledUpdates = $update.BundledUpdates
Categories = $update.Categories
CveIDs = $update.CveIDs
Deadline = $update.Deadline
DeltaCompressedContentAvailable = $update.DeltaCompressedContentAvailable
DeltaCompressedContentPreferred = $update.DeltaCompressedContentPreferred
DeploymentAction = $update.DeploymentAction
Description = $update.Description
DownloadPriority = $update.DownloadPriority
EulaAccepted = $update.EulaAccepted
EulaText = $update.EulaText
HandlerID = $update.HandlerID
Identity = $update.Identity
InstallationBehavior = $update.InstallationBehavior
IsBeta = $update.IsBeta
IsDownloaded = $update.IsDownloaded
IsHidden = $update.IsHidden
IsInstalled = $update.IsInstalled
IsMandatory = $update.IsMandatory
IsPresent = $update.IsPresent
IsUninstallable = $update.IsUninstallable
KBArticleIDs = $update.KBArticleIDs
Languages = $update.Languages
LastDeploymentChangeTime = $update.LastDeploymentChangeTime
MoreInfoUrls = $update.MoreInfoUrls
MsrcSeverity = $update.MsrcSeverity
RebootRequired = $update.RebootRequired
RecommendedCpuSpeed = $update.RecommendedCpuSpeed
RecommendedHardDiskSpace = $update.RecommendedHardDiskSpace
RecommendedMemory = $update.RecommendedMemory
ReleaseNotes = $update.ReleaseNotes
SecurityBulletinIDs = $update.SecurityBulletinIDs
SupersededUpdateIDs = $update.SupersededUpdateIDs
SupportUrl = $update.SupportUrl
Title = $update.Title
Type = $update.Type
}
}
if ($UpdateCollection.Count -gt 0) {
$Downloader = $Session.CreateUpdateDownloader()
$Downloader.Updates = $UpdateCollection
$Downloader.Download() | Out-Null
}
$Installer.AllowSourcePrompts = $true
$installer.ForceQuiet = $true
$Installer.Updates = $UpdateCollection
$isRebootRequired = $installer.RebootRequiredBeforeInstallation
return $isRebootRequired, $returnUpdates, $totalHistoryCount
}
$isRebootRequired, $returnUpdates, $totalHistoryCount = Invoke-Command -Session $session -ScriptBlock $serverScript
Exit-PSSession
$retValue = @{ ComputerName = $computerName; IsRebootRequired = $isRebootRequired; Updates = $returnUpdates; TotalHistoryCount = $totalHistoryCount }
return $retValue
}
$doInstallsScriptBlock = {
param (
$computerName
)
$session = New-PSSession -ComputerName $computerName -ConfigurationName 'VirtualAccount'
$serverScript = {
$UpdateCollection = New-Object -ComObject Microsoft.Update.UpdateColl
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$Session = New-Object -ComObject Microsoft.Update.Session
$Installer = New-Object -ComObject Microsoft.Update.Installer
$Updates = @($Searcher.Search("IsHidden=0 and IsInstalled=0").Updates)
foreach ($update in $Updates) {
$UpdateCollection.Add($update) | Out-Null
}
if ($UpdateCollection.Count -gt 0) {
$Downloader = $Session.CreateUpdateDownloader()
$Downloader.Updates = $UpdateCollection
$Downloader.Download() | Out-Null
}
$Installer.AllowSourcePrompts = $true
$installer.ForceQuiet = $true
$Installer.Updates = $UpdateCollection
Write-Host "Beginning install"
$result = $Installer.Install()
if ($result.ResultCode -ne 2) {
Write-Warning "$($env:COMPUTERNAME) ResultCode was not 2, it was $($result.ResultCode)"
}
Write-Host "$($env:COMPUTERNAME) finished installing, time to reboot?"
}
Invoke-Command -Session $session -ScriptBlock $serverScript
Exit-PSSession
return @{ ComputerName = $computerName; InstallCompleted = $true }
}
$results = Invoke-Parallel -ScriptBlock $rebootRequiredScriptBlock -objects $ComputerNames -ReturnObjects
Write-Output $results
# TODO: Reboot before continuing, if the flag above was true
if ($results.IsRebootRequired) {
throw "reboots are required on one or more servers. Please reboot before continuing"
}
if ($doUpdates) {
$results = Invoke-Parallel -ScriptBlock $doInstallsScriptBlock -objects $ComputerNames -ReturnObjects
}
if ($doRestarts) {
$computerNames = 'tea31697.fh.local','tea316155.fh.local','tea316208.fh.local','tea316229.fh.local','tea46658.fh.local'
foreach ($computerName in $computerNames) {
if ([string]::IsNullOrWhiteSpace($computerName)) {
Write-Host "Shutting down $computerName"
shutdown -m $computerName -t 0 -r
}
}
}
Write-Host "finished"