github.com/cloudfoundry-incubator/stembuild@v0.0.0-20211223202937-5b61d62226c6/modules/BOSH.WindowsUpdates/BOSH.WindowsUpdates.psm1 (about) 1 <# 2 .Synopsis 3 Install Windows Updates 4 .Description 5 This cmdlet installs all available Windows Updates in batches 6 #> 7 8 # Do not place these inside a function - they will not behave as expected 9 $script:ScriptName = $MyInvocation.MyCommand.ToString() 10 $script:ScriptPath = $MyInvocation.MyCommand.Path 11 12 function Find-WindowsUpdatesTask { 13 $task = Get-ScheduledTask -TaskName "InstallWindowsUpdates" -ErrorAction SilentlyContinue 14 return $task -ne $null 15 } 16 17 function Register-WindowsUpdatesTask { 18 $Prin = New-ScheduledTaskPrincipal -GroupId "BUILTIN\Administrators" -RunLevel Highest 19 $action = New-ScheduledTaskAction -Execute 'Powershell.exe' ` 20 -Argument "-Command `"Install-WindowsUpdates`" " 21 $trigger = New-ScheduledTaskTrigger -AtLogon -RandomDelay 00:00:30 22 Register-ScheduledTask -Principal $Prin -Action $action -Trigger $trigger -TaskName "InstallWindowsUpdates" -Description "InstallWindowsUpdates" 23 } 24 25 function Unregister-WindowsUpdatesTask { 26 $task = Find-WindowsUpdatesTask 27 if ($task) 28 { 29 Write-Log "Restart Scheduled Task Exists - Removing It" 30 Unregister-ScheduledTask -TaskName "InstallWindowsUpdates" -Confirm:$false 31 } 32 } 33 34 function Wait-WindowsUpdates { 35 Param([string]$Password,[string]$User) 36 37 Enable-Autologon -Password $Password -User $User 38 39 Write-Log "Getting WinRM config" 40 $winrm_config = Get-WinRMConfig 41 Write-Log "$winrm_config" 42 43 disable-service("WinRM") 44 45 Write-Log "Getting WinRM config" 46 $winrm_config = Get-WinRMConfig 47 Write-Log "$winrm_config" 48 } 49 50 function Install-WindowsUpdates { 51 52 # Set registry key so that we will receive the Jan 2018 patches (KB4056895) 53 REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\QualityCompat /f /v cadca5fe-87d3-4b96-b7fb-a231484277cc /t REG_DWORD /d 0 54 55 # Set registry keys so that KB4056898 will be enabled 56 # Set registry keys to cover CVE-2018-11091 CVE-2018-12126 CVE-2018-12127 CVE-2018-12130 CVE-2017-5753 CVE-2017-5715 CVE-2017-5754 CVE-2018-3639 CVE-2018-3615 CVE-2018-3620 CVE-2018-3646 57 reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverride /t REG_DWORD /d 72 /f 58 reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f 59 reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization" /v MinVmVersionForCpuBasedMitigations /t REG_SZ /d "1.0" /f 60 61 if (test-path "C:\provision\patch.msu") { 62 Write-Log "Already installed out-of-band patch" 63 } else { 64 Set-Service -Name wuauserv -StartupType Manual 65 Start-Service -Name wuauserv 66 67 Invoke-WebRequest -UseBasicParsing -Uri 'http://download.windowsupdate.com/d/msdownload/update/software/secu/2018/01/windows8.1-kb4056898-x64_ad6c91c5ec12608e4ac179b2d15586d244f0d2f3.msu' -Outfile C:\provision\patch.msu 68 wusa.exe C:\provision\patch.msu /quiet 69 start-sleep 200 70 } 71 72 $script:UpdateSession = New-Object -ComObject 'Microsoft.Update.Session' 73 $script:UpdateSession.ClientApplicationID = 'BOSH.WindowsUpdates' 74 $script:UpdateSearcher = $script:UpdateSession.CreateUpdateSearcher() 75 $script:SearchResult = New-Object -ComObject 'Microsoft.Update.UpdateColl' 76 77 $script:Cycles = 0 78 $script:CycleUpdateCount = 0 79 $script:MaxUpdatesPerCycle=500 80 $script:RestartRequired=0 81 $script:MoreUpdates=0 82 $script:MaxCycles=5 83 84 Get-UpdateBatch 85 if ($script:MoreUpdates -eq 1) { 86 Install-UpdateBatch 87 } else { 88 Invoke-RebootOrComplete 89 } 90 } 91 92 function Invoke-RebootOrComplete() { 93 $RegistryEntry = "InstallWindowsUpdates" 94 switch ($script:RestartRequired) { 95 0 { 96 Unregister-WindowsUpdatesTask 97 98 Write-Log "No Restart Required" 99 Get-UpdateBatch 100 101 if (($script:MoreUpdates -eq 1) -and ($script:Cycles -le $script:MaxCycles)) { 102 Install-UpdateBatch 103 } elseif ($script:Cycles -gt $script:MaxCycles) { 104 Write-Log "Exceeded Cycle Count - Stopping" 105 Enable-WinRM 106 Disable-Autologon 107 } else { 108 Write-Log "Done Installing Windows Updates" 109 Enable-WinRM 110 Disable-Autologon 111 } 112 113 Write-Log "Getting WinRM config" 114 $winrm_config = Get-WinRMConfig 115 Write-Log "$winrm_config" 116 } 117 1 { 118 $prop = Find-WindowsUpdatesTask 119 if (-not $prop ) { 120 Write-Log "Restart Scheduled Task Does Not Exist - Creating It" 121 Register-WindowsUpdatesTask 122 } else { 123 Write-Log "Restart Scheduled Task Exists Already" 124 } 125 126 Write-Log "Restart Required - Restarting..." 127 Restart-Computer 128 } 129 default { 130 Write-Log "Unsure If A Restart Is Required" 131 break 132 } 133 } 134 } 135 136 function Install-UpdateBatch() { 137 $script:Cycles++ 138 Write-Log "Evaluating Available Updates with limit of $($script:MaxUpdatesPerCycle):" 139 $UpdatesToDownload = New-Object -ComObject 'Microsoft.Update.UpdateColl' 140 $script:i = 0; 141 if ($Host.Version.Major -eq 5) { 142 $CurrentUpdates = $SearchResult.Updates 143 } else { 144 $CurrentUpdates = $SearchResult.Updates | Select-Object 145 } 146 while($script:i -lt $SearchResult.Updates.Count -and $script:CycleUpdateCount -lt $script:MaxUpdatesPerCycle) { 147 $Update = $CurrentUpdates[$script:i] 148 if (($null -ne $Update) -and (!$Update.IsDownloaded)) { 149 [bool]$addThisUpdate = $false 150 if ($Update.InstallationBehavior.CanRequestUserInput) { 151 Write-Log "> Skipping: $($Update.Title) because it requires user input" 152 } else { 153 if (!($Update.EulaAccepted)) { 154 Write-Log "> Note: $($Update.Title) has a license agreement that must be accepted. Accepting the license." 155 $Update.AcceptEula() 156 [bool]$addThisUpdate = $true 157 $script:CycleUpdateCount++ 158 } else { 159 [bool]$addThisUpdate = $true 160 $script:CycleUpdateCount++ 161 } 162 } 163 164 if ([bool]$addThisUpdate) { 165 Write-Log "Adding: $($Update.Title)" 166 $UpdatesToDownload.Add($Update) |Out-Null 167 } 168 } 169 $script:i++ 170 } 171 172 if ($UpdatesToDownload.Count -eq 0) { 173 Write-Log "No Updates To Download..." 174 } else { 175 Write-Log 'Downloading Updates...' 176 $ok = 0; 177 while (! $ok) { 178 try { 179 $Downloader = $UpdateSession.CreateUpdateDownloader() 180 $Downloader.Updates = $UpdatesToDownload 181 $Downloader.Download() 182 $ok = 1; 183 } catch { 184 Write-Log $_.Exception | Format-List -force 185 Write-Log "Error downloading updates. Retrying in 30s." 186 $script:attempts = $script:attempts + 1 187 Start-Sleep -s 30 188 } 189 } 190 } 191 192 $UpdatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl' 193 [bool]$rebootMayBeRequired = $false 194 Write-Log 'The following updates are downloaded and ready to be installed:' 195 foreach ($Update in $SearchResult.Updates) { 196 if (($Update.IsDownloaded)) { 197 Write-Log "> $($Update.Title)" 198 $UpdatesToInstall.Add($Update) |Out-Null 199 200 if ($Update.InstallationBehavior.RebootBehavior -gt 0){ 201 [bool]$rebootMayBeRequired = $true 202 } 203 } 204 } 205 206 if ($UpdatesToInstall.Count -eq 0) { 207 Write-Log 'No updates available to install...' 208 $script:MoreUpdates=0 209 $script:RestartRequired=0 210 Enable-WinRM 211 212 Write-Log "Getting WinRM config" 213 $winrm_config = Get-WinRMConfig 214 Write-Log "$winrm_config" 215 break 216 } 217 218 if ($rebootMayBeRequired) { 219 Write-Log 'These updates may require a reboot' 220 $script:RestartRequired=1 221 } 222 223 Write-Log 'Installing updates...' 224 225 $Installer = $script:UpdateSession.CreateUpdateInstaller() 226 $Installer.Updates = $UpdatesToInstall 227 $InstallationResult = $Installer.Install() 228 229 Write-Log "Installation Result: $($InstallationResult.ResultCode)" 230 Write-Log "Reboot Required: $($InstallationResult.RebootRequired)" 231 Write-Log 'Listing of updates installed and individual installation results:' 232 if ($InstallationResult.RebootRequired) { 233 $script:RestartRequired=1 234 } else { 235 $script:RestartRequired=0 236 } 237 238 for($i=0; $i -lt $UpdatesToInstall.Count; $i++) { 239 New-Object -TypeName PSObject -Property @{ 240 Title = $UpdatesToInstall.Item($i).Title 241 Result = $InstallationResult.GetUpdateResult($i).ResultCode 242 } 243 Write-Log "Item: $UpdatesToInstall.Item($i).Title" 244 Write-Log "Result: $InstallationResult.GetUpdateResult($i).ResultCode" 245 } 246 247 Invoke-RebootOrComplete 248 } 249 250 function Get-UpdateBatch() { 251 Write-Log "Checking For Windows Updates" 252 $Username = $env:USERDOMAIN + "\" + $env:USERNAME 253 254 New-EventLog -Source $script:ScriptName -LogName 'Windows Powershell' -ErrorAction SilentlyContinue 255 256 $Message = "Script: " + $script:ScriptPath + "`nScript User: " + $Username + "`nStarted: " + (Get-Date).toString() 257 258 Write-EventLog -LogName 'Windows Powershell' -Source $script:ScriptName -EventID "104" -EntryType "Information" -Message $Message 259 Write-Log $Message 260 261 $script:UpdateSearcher = $script:UpdateSession.CreateUpdateSearcher() 262 $script:successful = $FALSE 263 $script:attempts = 0 264 $script:maxAttempts = 12 265 while(-not $script:successful -and $script:attempts -lt $script:maxAttempts) { 266 try { 267 $script:SearchResult = $script:UpdateSearcher.Search("IsInstalled=0 and Type='Software' and IsHidden=0") 268 $script:successful = $TRUE 269 } catch { 270 Write-Log $_.Exception | Format-List -force 271 Write-Log "Search call to UpdateSearcher was unsuccessful. Retrying in 10s." 272 $script:attempts = $script:attempts + 1 273 Start-Sleep -s 10 274 } 275 } 276 277 if ($SearchResult.Updates.Count -ne 0) { 278 $Message = "There are " + $SearchResult.Updates.Count + " more updates." 279 Write-Log $Message 280 try { 281 for($i=0; $i -lt $script:SearchResult.Updates.Count; $i++) { 282 Write-Log $script:SearchResult.Updates.Item($i).Title 283 Write-Log $script:SearchResult.Updates.Item($i).Description 284 Write-Log $script:SearchResult.Updates.Item($i).RebootRequired 285 Write-Log $script:SearchResult.Updates.Item($i).EulaAccepted 286 } 287 $script:MoreUpdates=1 288 } catch { 289 Write-Log $_.Exception | Format-List -force 290 Write-Log "Showing SearchResult was unsuccessful. Rebooting." 291 $script:RestartRequired=1 292 $script:MoreUpdates=0 293 Invoke-RebootOrComplete 294 Write-Log "Show never happen to see this text!" 295 Restart-Computer 296 } 297 } else { 298 Write-Log 'There are no applicable updates' 299 $script:RestartRequired=0 300 $script:MoreUpdates=0 301 } 302 } 303 304 function Search-InstalledUpdates() { 305 $Session = New-Object -ComObject Microsoft.Update.Session 306 $Searcher = $Session.CreateUpdateSearcher() 307 $Searcher.Search("IsInstalled=1").Updates | Sort-Object LastDeploymentChangeTime | ForEach-Object { "KB$($_.KBArticleIDs) | $($_.Title)" } 308 } 309 310 function Test-InstalledUpdates() { 311 Write-Host "Running Get-HotFix:" 312 Get-HotFix 313 $Session = New-Object -ComObject Microsoft.Update.Session 314 Write-Host "Session: $Session" 315 $Searcher = $Session.CreateUpdateSearcher() 316 Write-Host "Searcher: $Searcher" 317 $UninstalledUpdates = $Searcher.Search("IsInstalled=0 and Type='Software' and IsHidden=0").Updates 318 if ($UninstalledUpdates.Count -ne 0) { 319 Write-Log "The following updates are not currently installed:" 320 foreach ($Update in $UninstalledUpdates) { 321 Write-Log "> $($Update.Title)" 322 } 323 Throw 'There are uninstalled updates' 324 } 325 } 326 327 <# 328 .Synopsis 329 Disable Automatic Updates 330 .Description 331 This cmdlet disables automatic Windows Updates 332 #> 333 function Disable-AutomaticUpdates() { 334 Stop-Service -Name wuauserv 335 Set-Service -Name wuauserv -StartupType Disabled 336 337 Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -Value 1 -Name 'AUOptions' 338 Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -Value 0 -Name 'EnableFeaturedSoftware' 339 Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update' -Value 0 -Name 'IncludeRecommendedUpdates' 340 } 341 342 function Enable-CVE-2015-6161() { 343 #Enable MS15-124 - Internet Explorer ASLR Bypass fix - CVE-2015-6161 344 reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING" /t REG_DWORD /v "iexplore.exe" /d 1 /f 345 reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ALLOW_USER32_EXCEPTION_HANDLER_HARDENING" /t REG_DWORD /v "iexplore.exe" /d 1 /f 346 } 347 348 function Enable-CVE-2017-8529() { 349 #Enable Microsoft Browser Information Disclosure Vulnerability - CVE-2017-8529 350 reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX" /v iexplore.exe /t REG_DWORD /d 1 /f 351 reg add "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_PRINT_INFO_DISCLOSURE_FIX" /v iexplore.exe /t REG_DWORD /d 1 /f 352 353 } 354 355 function Enable-CredSSP() { 356 #Enable CredSSP updates - CVE-2018-0886 357 #Policy set to "mitigated" 358 reg add "HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System\CredSSP\Parameters" /v AllowEncryptionOracle /t REG_DWORD /d 1 /f 359 } 360 361 function Upgrade-PSVersion () { 362 if (Test-PSVersion) { 363 Write-Log "Upgrade-PSVersion: No need to upgrade. PSVersion is 5 or above" 364 return 365 } 366 367 $existingProtocol = [Net.ServicePointManager]::SecurityProtocol 368 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 369 Write-Log "Upgrade-PSVersion: Downloading." 370 371 $MSUPath = "c:\provision\PS51.msu" 372 Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/?linkid=839516" -UseBasicParsing -OutFile $MSUPath 373 374 Write-Log "Upgrade-PSVersion: Downloaded. Installing." 375 376 $p = Start-Process -FilePath $MSUPath -ArgumentList '/quiet /norestart /log:"C:\provision\psupgrade.log"' -Wait -PassThru 377 Write-Log "Upgrade-PSVersion: Installed. Process exit code: $($p.ExitCode)" 378 [Net.ServicePointManager]::SecurityProtocol = $existingProtocol 379 } 380 381 function Test-PSVersion { 382 $version = $PSVersionTable.PSVersion 383 Write-Log "Powershell is $version" 384 $version.Major -ge 5 385 }