<# .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"