Automate Windows Updates for Development
I’ve run into the case where I wanted updates continually applied, while the machine still was part of the GPO that didn’t automatically install updates. For this developer and test oriented machine I wanted every update applied.
I utilized a great module for this and created a script to setup the task and logging to make this an easy task.
If you experience an issue with the WindowsUpdate Vs Microsoft update as the configured update provider, then you can just change the switch in the script for -MicrosoftUpdate to -WindowsUpdate
This isn’t something I’d run in production, but I’ve found it helpful to updating a development server with the latest SQL Server updates, as well as a development machine, allowing me to keep up with any latest changes with minimal effort.
Change the reboot parameter to your preferred option in the script. I left as autoreboot for the purpose of a low priority dev server being updated.
$script:PSModuleRoot = ($PSScriptRoot, 'C:\powershell\STARTUP_InstallWindowsUpdates' -ne '')[0]
$WorkingDirectory = [io.directory]::CreateDirectory('C:\powershell\STARTUP_InstallWindowsUpdates').FullName
$IconDirectory = [io.directory]::CreateDirectory("$WorkingDirectory\icons").FullName
$TaskName = 'STARTUP_InstallWindowsUpdates'
Clear-EventLog -LogName STARTUP_InstallWindowsUpdates -ErrorAction SilentlyContinue
New-EventLog -LogName STARTUP_InstallWindowsUpdates -Source STARTUP_InstallWindowsUpdates -ErrorAction SilentlyContinue -Verbose
Copy-Item "C:\Users\shull\Documents\Powershell\STARTUP_InstallWindowsUpdates_requirements.psd1" -Destination $WorkingDirectory -force
[scriptblock]$InstallWindowsUpdates = {
$ErrorActionPreference = 'Stop'
$TaskName = 'STARTUP_InstallWindowsUpdates'
$AppLogo = "$IconDirectory\ic_whatshot_black_24dp_2x.png"
if (!(Test-path env:\BypassPsDepend))
{
if ($Env:BypassPsDepend -eq $true)
{
write-host "env:\BypassPsDepend set to true, bypassing any dependency setup per user request"
}
else
{
[datetime]$StepTimer = [datetime]::Now
write-host "Running through requirements.psd1 to setup dependencies"
if (@(Get-Module PsDepend -ListAvailable).count -eq 0)
{ Install-Module PsDepend -verbose:$false}
Import-module PsDepend -verbose:$false
$ParsedRequirementFilePath = [io.path]::combine($script:PSModuleRoot, 'STARTUP_InstallWindowsUpdates_requirements.psd1')
if ([io.file]::Exists($ParsedRequirementFilePath))
{ Invoke-PsDepend -path $ParsedRequirementFilePath -confirm:$false -verbose:$false -Quiet }
else { write-error "Unable to parse requirement file path from: $ParsedRequirementFilePath. Current path $($PWD.Path)" }
write-host( "{0:hh\:mm\:ss\.fff} {1}: finished" -f [timespan]::FromMilliseconds(((Get-Date) - $StepTimer).TotalMilliseconds), "Invoke-PSDepend")
Import-Module 'PowershellHumanizer' -disablenamechecking -verbose:$false
}
}
Import-Module 'BurntToast' -disablenamechecking -verbose:$false
Import-Module 'PsWindowsUpdate' -disablenamechecking -verbose:$false
Write-EventLog -LogName STARTUP_InstallWindowsUpdates -source STARTUP_InstallWindowsUpdates -EntryType Information -Message "initiating posh windows update" -EventId 1
if(![io.file]::Exists("$IconDirectory\whatshop.png"))
{
Expand-Archive "$WorkingDirectory\whatshot.zip" -Destination $IconDirectory -force
$Icon = @(get-childitem $IconDirectory -recurse -include *.png | where {$_.Name -eq 'ic_whatshot_black_24dp_2x.png' } | select -First 1).FullName
Copy-Item -Path $Icon -Destination $AppLogo
}
New-BTAppId -AppId $TaskName -WarningAction SilentlyContinue
function Start-Sleeping{
[cmdletbinding()]
param(
$Minutes
,$Hours
,$seconds
,$Activity = 'Sleeping'
,$Status = '..sleeping'
)
$doneDt = Get-Date
$doneDt = $doneDT.AddHours($hours)
$doneDt = $doneDT.AddMinutes($Minutes)
$doneDT = $DoneDt.AddSeconds($seconds)
$doneDateMessage = $doneDT.ToString('yyyy-MM-dd hh:mm:ss')
$TotalSeconds = ((get-date($doneDt))-(get-date)).TotalSeconds
while($doneDT -gt (Get-Date)) {
$secondsLeft = $doneDT.Subtract((Get-Date)).TotalSeconds
[int]$percent = ($TotalSeconds - $secondsLeft) / $TotalSeconds * 100
Write-Progress -Activity $Activity -Status $Status -SecondsRemaining $secondsLeft -PercentComplete $percent
[System.Threading.Thread]::Sleep(1000)
}
Write-Progress -Activity $Activity -Status $Status -SecondsRemaining 0 -Completed
}
try {
$TotalToProcess = 3
[datetime]$StepTimer = [datetime]::Now
$Counter = 1
[double]$PercentDone = ($Counter /$TotalToProcess)
$Prog = New-BTProgressBar -Title $TaskName -Status 'Running' -Value ([double]$PercentDone) -ValueDisplay ("$counter of $TotalToProcess done")
New-BurntToastNotification -ProgressBar $Prog -UniqueIdentifier $TaskName -AppLogo $AppLogo -Text "Windows Updates", 'checking for available Windows updates'
$Updates = Get-WUInstall -MicrosoftUpdate -AcceptAll -ListOnly -UpdateType Software -NotCategory "Language packs"
#PsWindowsUpdate\Get-WUServiceManager
$UpdateCount = @($Updates).Count
write-information ("---- Updates Identified $($UpdateCount) ----`n$($Updates | select Title, Size | Format-Table -AutoSize | Out-string)")
Write-EventLog -LogName STARTUP_InstallWindowsUpdates -source STARTUP_InstallWindowsUpdates -EntryType Information -Message ("---- Updates Identified $($UpdateCount) ----`n$($Updates | select Title, Size | Format-Table -AutoSize | Out-string)") -EventId 1
if ($Updates)
{
[double]$PercentDone = (++$Counter /$TotalToProcess)
$Prog = New-BTProgressBar -Title $TaskName -Status 'Running' -Value ([double]$PercentDone) -ValueDisplay ("$counter of $TotalToProcess done")
New-BurntToastNotification -AppId $TaskName -ProgressBar $Prog -UniqueIdentifier $TaskName -AppLogo $AppLogo -Silent -Text ("Windows Updates","available updates: $UpdateCount",$Updates.Title )
$UpdatesMessage = $Updates.Title | Format-List | Out-string #| out-file -FilePath $log -append -force
Write-EventLog -LogName STARTUP_InstallWindowsUpdates -source STARTUP_InstallWindowsUpdates -EntryType Information -Message ($UpdatesMessage) -EventId 1
Get-WUInstall -MicrosoftUpdate -AcceptAll -UpdateType Software -NotCategory "Language packs"
[double]$PercentDone = (++$Counter /$TotalToProcess)
$Prog = New-BTProgressBar -Title $TaskName -Status 'Running' -Value ([double]$PercentDone) -ValueDisplay ("$counter of $TotalToProcess done")
New-BurntToastNotification -AppId $TaskName -ProgressBar $Prog -UniqueIdentifier $TaskName -AppLogo $AppLogo -Silent -Text ("Windows Updates","finished installing: $UpdateCount")
Write-EventLog -LogName STARTUP_InstallWindowsUpdates -source STARTUP_InstallWindowsUpdates -EntryType Information -Message ("finished installing: $UpdateCount") -EventId 1
}
else {
$Message = "$(get-date) No updates detected. Exiting script"
Write-EventLog -LogName STARTUP_InstallWindowsUpdates -source STARTUP_InstallWindowsUpdates -EntryType Information -Message "$(get-date) No updates detected. Exiting script" -EventId 1
[double]$PercentDone = (++$Counter /$TotalToProcess)
$Prog = New-BTProgressBar -Title $TaskName -Status 'Running' -Value ([double]1) -ValueDisplay "done"
New-BurntToastNotification -AppId $TaskName -ProgressBar $Prog -UniqueIdentifier $TaskName -AppLogo $AppLogo -Silent -Text ("Windows Updates","no updates detected exiting script")
#Start-Sleeping -Minutes 5 -Activity "Windows Update" -Status "No updates detected. Sleeping before exit"
#$Message | out-string | out-file -FilePath $Log -append -force
}
}
catch {
Write-EventLog -LogName STARTUP_InstallWindowsUpdates -source STARTUP_InstallWindowsUpdates -EntryType Error -Message "$(get-date) Error in running updates. $($_.Exception | Format-List | out-string)" -EventId 1
write-warning ($_.Exception | Format-List | out-string)
$Prog = New-BTProgressBar -Title $TaskName -Status 'ERROR' -Value ([double]1) -ValueDisplay "FAILED"
New-BurntToastNotification -AppId $TaskName -ProgressBar $Prog -UniqueIdentifier $TaskName -AppLogo $AppLogo -Text ("Windows Updates",$_.Exception | format-list | out-string)
throw
}
write-verbose( "{0:hh\:mm\:ss\.fff} {1}: finished" -f [timespan]::FromMilliseconds(((Get-Date)-$StepTimer).TotalMilliseconds),'WindowsUpdate')
}
$ScriptFile = "$WorkingDirectory\$TaskName.ps1"
$InstallWindowsUpdates | out-string | Out-File $ScriptFile -Force -verbose
gwmi Win32_Process -Filter "name = 'powershell.exe'" | Where-Object {$_.CommandLine -match $taskname } | %{ $_.Terminate()}
stop-scheduledtask -TaskName $taskname -ea SilentlyContinue;
$Triggers = @()
$Triggers+= (New-ScheduledTaskTrigger -Daily -at '10:00' -RandomDelay 00:00:30 -ThrottleLimit 1)
$Triggers+= (New-ScheduledTaskTrigger -Daily -at '13:00' -RandomDelay 00:00:30 -ThrottleLimit 1)
$Triggers+= (New-ScheduledTaskTrigger -Daily -at '16:00' -RandomDelay 00:00:30 -ThrottleLimit 1)
$Action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument ('-windowstyle hidden -Command "Start-process powershell.exe -argumentlist "c:\powershell\InstallWindowsUpdates.ps1" -WindowStyle Hidden -verb RunAs' -f $ScriptFile) #-WindowStyle Minimized #-noprofile -NonInteractive --'C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe'
Write-EventLog -LogName STARTUP_InstallWindowsUpdates -source STARTUP_InstallWindowsUpdates -EntryType Information -Message "Registered scheduled task" -EventId 1
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([timespan]::fromhours(1)) -ThrottleLimit 1
$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -Trigger $Triggers -Action $Action -TaskName $taskname -Force -Principal $principal -Settings $settings #-RunLevel Highest - #-User (read-host "Enter Admin Level account to run under") -Password (Read-Host -Prompt "Enter Password")
start-scheduledtask -TaskName $taskname -Verbose