UnattendedUpdate.ps1
param( [Switch]$Console = $False, #--[ Set to true to enable local console result display. Defaults to false ]-- [Switch]$Debug = $False, #--[ Set to true to only send results to debug email address. Default to false ]-- [Switch]$Manual = $False, #--[ Use to run update off-schedule ]-- [Switch]$Status = $False, #--[ If set to true checks for results after the reboot and emails, then goes idle. ]-- [Switch]$Deploy = $False, #--[ If set to true will copy this script to the other members of the peer group ]-- [Switch]$UpTimeCheck = $False, #--[ If set to true will send an email alert if no restart occurs in preset # of days ]-- [Switch]$NoRestart = $False #--[ Stops restart from occurring. Restart may still occur if update determines it's needed. ]-- ) <#====================================================================================== File Name : UnattendedUpdate.ps1 Original Author : Kenneth C. Mazie (kcmjr AT kcmjr.com) : Description : Will scan the Windows Update site and install all missing updates. : Operation : Requires PowerShell v5. Requires NuGet and PSWindowsUpdate modules. Will auto-install them if needed. : Reboots system following patching to assure new updates are applied. Creates a LOCAL scheduled task : automatically on first run with script version in name. Will delete and recreate the task if : a script version change is detected. The patch schedule is set to randomize the run : within a 90 minute window. Update routine is governed by the week days noted in the config file. : The scheduled task has two triggers, run time, and on restart. An HTML report is sent on every restart. : Antivirus is disabled during the update run. Kaspersky is configure so change that as needed. : Requires a config file in XML format located in the same folder as the script. See example at bottom. : Arguments : Normal operation is with no command line options. : -Console $true (Will enable local console output) : -Debug $true (Not used) : -Manual $true (forces a manual run bypassing the schedule) : -Status $true (forces a status email to be sent) : -Deploy $true (forces the script and config file to be copied to the identical : location on the other peer servers listed in the config file) : -UpTimeCheck $true (If set to true will send an email alert if no restart occurs in preset # of days) : -NoRestart $true (Stops restart from occurring. Restart may still occur if update determines it's needed.) : Warnings : Uses local SYSTEM user context for tasks. Install LOCALLY, not remotely. : Adjust the task schedule(s) to conform to your maintenance window. : Legal : Public Domain. Modify and redistribute freely. No rights reserved. : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED. : Credits : Code snippets and/or ideas came from many sources including but : not limited to the following: : https://www.powershellgallery.com/packages/PSWindowsUpdate/1.5.2.2 : Last Update by : Kenneth C. Mazie (kcmjr AT kcmjr.com) : Version History : v1.00 - 12-29-16 - Original Change History : v2.00 - 01-13-17 - Added forced status option for Sundays. : v2.10 - 01-17-17 - Added regkey delete. : v2.20 - 04-06-17 - Fixed row data to start clean at each loop : v2.30 - 09-28-17 - Turned off extra email after patching. : Moved config file out. Added script replication option. : Added run day from config file option. : v2.40 - 10-19-17 - Added randomizer for reboot. Added check for no data. : v2.50 - 12-01-17 - Fixed runday detection : v2.60 - 02-27-18 - Eliminated second schedule to send status at restart due to bugs in PS : task schedule commandlets not detecting all schedules. : v2.70 - 02-28-18 - Added reboot detection for automatic status report. : v2.80 - 03-01-18 - Added automatic scheduling adjustmant. Added restart bypass. : v2.90 - 03-05-18 - Fixed registry key removal error. : v3.00 - 10-12-18 - Changed AV disable to default to false to stop false emails. Added option : to select wsus (in-house) or windows update (internet) as update source : #> $Script:ScriptVer = "3.00" <# =======================================================================================#> <#PSScriptInfo .VERSION 3.00 .GUID 9fd19521-b906-4e4f-8969-d71a8faa6195 .AUTHOR Kenneth C. Mazie (kcmjr AT kcmjr.com) .DESCRIPTION Automatically applies current patches to a single Windows system, then reboots. Emails a status report upon restart. Should be run from a scheduled task. Can deploy itself to "peer" systems. #> #Requires -version 5.0 clear-host if ($Console){$Script:Console = $true} if ($Debug){$Script:Debug = $true} if ($Manual){$Script:Manual = $true} if ($Status){$Script:Status = $true} if ($Deploy){ $Script:Deploy = $true $Script:Console = $true } if ($UpTimeCheck){$Script:UpTimeCheck = $true} if ($NoRestart){$Script:NoRestart = $true} $ErrorActionPreference = "SilentlyContinue" $Now = Get-Date -Format "MM-dd-yyyy_HHmm" $Script:ThisComputer = $Env:Computername $Script:MessageBody = @() $Today = (get-date).dayofweek $Script:Attach = $false $Script:ResultLog = "$PSScriptRoot\Results-$Now.csv" #--[ This next line is used to stop the local antivirus client. Edit the function below to support your Av client ]-- #--[ If the AV changes or is not used leaving this enabled will cause an extra junk email to go out ]-- $Script:KillKaspersky = $false #--------------------------------------------------------------------------------------------------------------- $Script:ScriptName = $MyInvocation.MyCommand.Name $Script:ScriptFullPath = $PSScriptRoot+"\"+$MyInvocation.MyCommand.Name $ConfigFile = $Script:ScriptFullPath.Split(".")[0]+".xml" $Script:UserContext = [Security.Principal.WindowsIdentity]::GetCurrent() #==[ Functions ]================================================================ Function LoadConfig { #--[ Read and load configuration file ]------------------------------------- if (!(Test-Path $ConfigFile)){ #--[ Error out if configuration file doesn't exist ]-- $Script:HTMLData = "MISSING CONFIG FILE. Script aborted." if ($Script:Log){Add-content -Path "$PSScriptRoot\debug.txt" -Value "MISSING CONFIG FILE. Script aborted."} write-host "CONFIGURATION FILE $ConfigFile NOT FOUND - EXITING" -ForegroundColor Red break;break;break }else{ [xml]$Script:Configuration = Get-Content $ConfigFile #--[ Read & Load XML ]-- $Script:PeerList = $Script:Configuration.Settings.General.PeerList $Script:RunDays = $Script:Configuration.Settings.General.RunDays $Script:RunTime = $Script:Configuration.Settings.General.RunTime $Script:UpdateSource = $Script:Configuration.Settings.General.UpdateSource $Script:DebugEmail = $Script:Configuration.Settings.Email.Debug $Script:eMailRecipient = $Script:Configuration.Settings.Email.To $Script:eMailFrom = $ThisComputer+'_'+$Script:Configuration.Settings.Email.From $Script:eMailHTML = $Script:Configuration.Settings.Email.HTML $Script:eMailSubject = $ThisComputer+' '+($Script:Configuration.Settings.Email.Subject) $Script:SmtpServer = $Script:Configuration.Settings.Email.SmtpServer $Script:UserName = $Script:Configuration.Settings.Credentials.Username $Script:EncryptedPW = $Script:Configuration.Settings.Credentials.Password $Script:Base64String = $Script:Configuration.Settings.Credentials.Key $Script:ReportName = $ThisComputer+' '+($Script:Configuration.Settings.General.ReportName) $Script:UpTimeDays = $Script:Configuration.Settings.General.UpTimeDays } $ByteArray = [System.Convert]::FromBase64String($Script:Base64String); $Script:Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Script:UserName, ($Script:EncryptedPW | ConvertTo-SecureString -Key $ByteArray) $Script:Password = $Script:Credential.GetNetworkCredential().Password } function SendEmail { $SMTP = new-object System.Net.Mail.SmtpClient($Script:SmtpServer) $Email = New-Object System.Net.Mail.MailMessage $Email.Body = $Script:MessageBody $Email.IsBodyHtml = $Script:eMailHTML #If ($Script:Debug){ $Email.To.Add($Script:DebugEmail) #}Else{ # $Email.To.Add($Script:eMailRecipient) #} if ($Script:Attach){ $Attachment = New-Object System.Net.Mail.Attachment -ArgumentList $Script:ResultLog, 'Application/Octet' $Email.Attachments.Add($Attachment) } $Email.From = $Script:eMailFrom $Email.Subject = $Script:eMailSubject $SMTP.Send($Email) $Email.Dispose() $SMTP.Dispose() } function GetResults { $Script:ResultOut = "" $Script:ResultLine = "" $RowFlag = $false #--[ Add header to html output file ]-------------------- $Script:MessageBody += '<tr><th>KB Number</th><th>Status</th><th>Results</th><th>Date / Time</th><th>Messages</th></tr>' #--[ HTML row Settings ]---------------------------------------------------- $BGColor = "#dfdfdf" #--[ Grey default cell background ]-- $BGColorRed = "#ff0000" #--[ Red background for alerts ]-- $BGColorOra = "#ff9900" #--[ Orange background for alerts ]-- $BGColorYel = "#ffd900" #--[ Yellow background for alerts ]-- $FGColor = "#000000" #--[ Black default cell foreground ]-- #--[ Only keep 10 of the last runtime logs ]------------------------------------ Get-ChildItem -Path $PSScriptRoot | Where-Object {(-not $_.PsIsContainer) -and ($_.Name -like "*results*.csv")} | Sort-Object -Descending -Property LastTimeWrite | Select-Object -Skip 10 | Remove-Item #--[ Scan Eventlogs for Event 19 & 20 ]-- $Script:LogDump = Get-WinEvent -FilterHashtable @{LogName = "System";ID=19,20} $Script:KBTracker = @() foreach ($Script:LogItem in $Script:LogDump ){ if ($Script:LogItem.ProviderName -eq 'Microsoft-Windows-WindowsUpdateClient'){ $Script:LogItemStat = $Script:LogItem.message.split(":")[0] if ($Script:LogItem.Message -like "*(KB*"){ $Script:LogItemKB = ($Script:LogItem.message.split("(")[1]).split(")")[0] if (!($Script:LogItemKB -like "KB*")){ $Script:LogItemKB = ($Script:LogItem.message.split("(")[2]).split(")")[0] } }else{ $Script:LogItemKB = $Script:LogItem.message.split(":")[2] #"---------" } If ($Script:KBTracker -notcontains ($Script:LogItemKB+" "+$Script:LogItemStat)){ #--[ Have not seen this KB yet ]-- $RowFlag = $true $RowData = '<tr>' #--[ Start table row ]-- if ($Script:Console){write-host $Script:LogItemKB" " -ForegroundColor Red -NoNewline } $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItemKB + '</td>' #--[ KB number to html table ]-- if ($Script:Console){write-host $Script:LogItem.LevelDisplayName" " -ForegroundColor yellow -NoNewline } if ($Script:Console){write-host $Script:LogItemStat" " -ForegroundColor Cyan -NoNewline } if ($Script:LogItem.LevelDisplayName -like "Error"){ $RowData += '<td bgcolor=' + $BGColor + '><font color=#800000>' + $Script:LogItem.LevelDisplayName + '</td>' #--[ Status (if error) to html table ]-- $RowData += '<td bgcolor=' + $BGColor + '><font color=#800000>' + $Script:LogItemStat + '</td>' #--[ Results to html table ]-- }else{ $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItem.LevelDisplayName + '</td>' #--[ Status to html table ]-- $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItemStat + '</td>' #--[ Results to html table ]-- } if ($Script:Console){write-host $Script:LogItem.TimeCreated" " -ForegroundColor yellow -NoNewline } #--[ Date/time to html table ]-- $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItem.TimeCreated + '</td>' if ($Script:Console){write-host $Script:LogItem.Message" " -ForegroundColor green} $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItem.message + '</td>' #--[ Result message to html table ]-- $Script:ResultLine = $Script:LogItemKB+","+$Script:LogItem.LevelDisplayName+","+$Script:LogItem.TimeCreated+","+$Script:LogItemStat+","+$Script:LogItem.Message $Script:ResultOut = $Script:ResultOut+$Script:ResultLine+"`n" $RowData += '</tr>' $Script:KBTracker += ($Script:LogItemKB+" "+$Script:LogItemStat) $Script:MessageBody += $RowData $Script:KBTracker } } } If ($RowFlag){ $Script:Attach = $true }Else{ $Script:MessageBody += '<td colspan=5 bgcolor=' + $BGColor + '><font color=' + $FGColor + '><center>No New Data to Report </center></td>' } Add-Content -value $Script:ResultOut -path $Script:ResultLog $Script:MessageBody += '</table><br>' $Script:MessageBody += "<font size=3 face='times new roman'>- Done. Emailing results...<br>" if ($Script:Console){Write-Host "`n- Done. Emailing results...`n" -ForegroundColor yellow } SendEmail } Function ServiceMgr ($Svc, $SvcStatus) { $Script:MessageBody += "- Processing: $Svc<br>" if ($Script:Console){Write-Host "`n- Processing :$Svc" -ForegroundColor cyan } $Count = 0 #--[ State prior to start/stop process ]-- $Script:SvcState = (Get-Service -Name $Svc).Status $Script:MessageBody += "-- $Svc Initial Status: $Script:SvcState<br>" if ($Script:Console){Write-Host "-- $Svc Initial Status: $Script:SvcState" -ForegroundColor yellow } $Script:MessageBody += "--- Pausing while attemtping to set service state to: $SvcStatus...<br>" if ($Script:Console){Write-Host "--- Pausing while attemtping to set service state to: $SvcStatus..." -ForegroundColor yellow } while ((Get-Service -Name $Svc).Status -ne $SvcStatus){ Get-Service -Name $Svc | Set-Service -Status $SvcStatus sleep -Milliseconds 500 $Count ++ if ($Count -ge 20){ if ($Script:Console){Write-Host "-- There was an error setting the "$Svc" service to the "$SvcStatus" state..." -ForegroundColor red } $Script:MessageBody += '-- There was an error setting the "$Svc" service to the "$SvcStatus" state...<br>' break } } #--[ State after start/stop process ]-- $Script:SvcState = (Get-Service -Name $Svc).Status $Script:MessageBody += "-- $Svc Final Status: $Script:SvcState<br>" if ($Script:Console){Write-Host "-- $Svc Final Status: $Script:SvcState" -ForegroundColor yellow } Sleep -Seconds 1 if ($Script:SvcState -eq "stopped"){ RegKill } } function RegKill { if ($Script:Console){Write-Host "`n- Removing Windows Update registry key..." -ForegroundColor cyan } try{ Clear-ItemProperty -Name 'WUServer' -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Force Clear-ItemProperty -Name 'WUStatusServer' -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Force Remove-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Force -Recurse }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody += "- Failed to remove Windows Update Key(s). $ErrorMessage<br>" if ($Script:Console){Write-Host "- Failed to remove Windows Update Key(s). $ErrorMessage" -ForegroundColor Red } } if (Test-Path -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'){ if ($Script:Console){Write-Host "-- FAILED to remove Windows Update registry key..." -ForegroundColor red } $Script:MessageBody += "- Failed to remove Windows Update Key(s).<br>" }else{ if ($Script:Console){Write-Host "-- Verified removal of Windows Update registry key..." -ForegroundColor green } $Script:MessageBody += "- Verified removal of Windows Update registry key(s).<br>" } } function PatchIt { if ($Script:KillKaspersky){ ServiceMgr "klnagent" "stopped" #--[ Stops Kaspersky agent prior to running update. Comment out above if not applicable. ]-- }else{ $SvcState = "stopped" } if ($SvcState -eq "stopped"){ #--[ NuGet is required to pull the module from the MS repository ]-- if (!(Get-PackageProvider NuGet)){ if (!(Get-ChildItem -Path "C:\Program Files\PackageManagement\ProviderAssemblies\nuget" -Filter "Microsoft.PackageManagement.NuGetProvider.dll" -Recurse)){ try{ Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -ErrorAction SilentlyContinue -Confirm:$false -Force:$true $Script:MessageBody += "- NuGet provider is being installed.<br>" if ($Script:Console){Write-Host "- NuGet provider is being installed." -ForegroundColor yellow } }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody += "- NuGet module install FAILED. $ErrorMessage<br>" if ($Script:Console){Write-Host "- NuGet module install FAILED. $ErrorMessage" -ForegroundColor Red } } } } #--[ Install the update module if it's not already loaded ]-- if (!(Get-Module PSWindowsUpdate)){ try{ $Script:MessageBody += '- "PSWindowsUpdate" module is being installed.<br>' if ($Script:Console){Write-Host '- "PSWindowsUpdate" module is being installed...' -ForegroundColor yellow } Install-Module PSWindowsUpdate -ErrorAction Stop -Confirm:$false -Force:$true }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody += '- "PSWindowsUpdate" module install FAILED. $ErrorMessage<br>' if ($Script:Console){Write-Host '- "PSWindowsUpdate" module install FAILED. $ErrorMessage' -ForegroundColor Red } } } #--[ Register to use the Microsoft Update Service, as opposed to just the default Windows Update Service. ]-- if (!((Get-WUServiceManager).ServiceID -contains "7971f918-a847-4430-9279-4a52d1efe18d")){ try{ $Script:MessageBody += "- Service Manager ID is being registered.<br>" if ($Script:Console){Write-Host "- Service Manager ID is being registered." -ForegroundColor yellow } Add-WUServiceManager -ServiceID '7971f918-a847-4430-9279-4a52d1efe18d' -Confirm:$false -ErrorAction Stop }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody += "- Service Manager ID registration FAILED. $ErrorMessage<br>" if ($Script:Console){Write-Host "- Service Manager ID registration FAILED. $ErrorMessage" -ForegroundColor Red } } } #--[ Run the update in unattended mode, check for all updates on the MS update site, accept all EULAs, reboot if needed ]-- $Script:MessageBody += "- Checking for and installing new updates.<br>" $Script:MessageBody += "-- A reboot is required to register new patches, as such the system will be rebooted shortly.<br>" $Script:MessageBody += "-- A summary report will be dispatched shortly after the system comes back online.<br>" if ($Script:Console){ Write-Host "`n- Checking for and installing new updates." -ForegroundColor cyan Write-Host "-- Note that this process produces no console output." -ForegroundColor yellow Write-Host "-- A reboot is required to register new patches, as such the system will be rebooted shortly." -ForegroundColor yellow Write-Host "-- A summary report will be dispatched shortly after the system comes back online." -ForegroundColor yellow } $Script:Attach = $false #SendEmail #--[ Disabled so that the emails only go out are after reboot or on svc error ]-- #--[ Select update source, either WSUS or MS Update web site. Selected from config file ]-- If ($Script:UpdateSource -eq "wsus"){ Get-WUInstall -WindowsUpdate -AcceptAll -Confirm:$false -AutoReboot:$true }Else{ Get-WUInstall -MicrosoftUpdate -AcceptAll -Confirm:$false -AutoReboot:$true } }else{ $Script:MessageBody += "-- AntiVirus service failed to stop -- ABORTING --" if ($Script:Console){Write-Host "`n-- $Svc Failed to stop -- ABORTING --`n" -ForegroundColor Red } SendEmail } #--[ Things to do if no reboot aurtomatically occurs after running update. Comment items out if not applicable. ]-- Sleep -Seconds 60 #--[ Wait to see if no reboot has occurred ]-- if ($Script:KillKaspersky){ ServiceMgr "klnagent" "running" #--[ Restart it if we don't reboot ]-- }else{ $SvcState = "running" } #--[ Force a reboot with random delay if none occurred. ]-- $RndArray = @(300,600,900) #--[ 300 seconds = 5 minutes]-- $Rnd = (new-object System.Random) $RndDelay = $Array[ $Rnd.Next( $Array.Count ) ] $RndDelay = 5 if ($Script:Console){Write-Host `n'--- REBOOTING COMPUTER --- ('$RndDelay' second delay)...' -ForegroundColor red } Sleep -Seconds $RndDelay If ($NoRestart){ if ($Script:Console){Write-Host `n'--- REBOOT BYPASS ENABLED --- ' -ForegroundColor yellow } }Else{ Try{ #Restart-Computer -Credential $Script:Credential -Confirm $false -force #-WhatIf #--[ Not working for the local PC ]-- Invoke-Command -ComputerName "localhost" -Credential $Script:Credential -ScriptBlock {shutdown -r -t 3} }Catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName if ($Script:Console){Write-Host '- System Restart has failed to execute... $ErrorMessage' -ForegroundColor Red } } } } Function ScheduledTask ($ActiveTask){ #$ActiveTask = "UnattendedUpdate-2.9" #--------------------------- for testing --------------------------------- $CreateTask = $True $Script:TaskMessage = "" $ExistingTasks = (Get-ScheduledTask | Where-Object {$_.TaskName -like ('*'+$ActiveTask.Split("-")[0]+'*')}) ForEach ($FoundTask in $ExistingTasks){ If (($FoundTask.taskname.Split("-")[1]) -match '\d'){ If ($FoundTask.TaskName -eq $ActiveTask ){ if ($Script:Console){Write-Host '- Task "'$FoundTask.TaskName'" already exists... IGNORING' -ForegroundColor Green } $Script:TaskMessage += '- Task "'+$FoundTask.TaskName+'" already exists...<br>' $CreateTask = $False }Else{ if ($Script:Console){Write-Host '- Task "'$FoundTask.TaskName'" is an incorrect version... REMOVING' -ForegroundColor red } $Script:TaskMessage += '- Task "'+$FoundTask.TaskName+'" is an incorrect version... REMOVING<br>' Try{ #$Command = Get-ScheduledTask | Where-Object {$_.TaskName -eq $FoundTask.TaskName} #Unregister-ScheduledTask -taskname $Command -taskpath "\" #-Confirm:$false #--[ not working [-- $Result = Invoke-Expression ("schtasks.exe /delete /s "+$Env:ComputerName+" /tn "+$FoundTask.TaskName+" /F") if ($Script:Console){Write-Host "-- "$Result -ForegroundColor white } $Script:TaskMessage += '-- "'+$Result+'<br>' $CreateTask = $true }Catch{ $_.Exception.Message $_.Exception.ItemName } } }Else{ if ($Script:Console){Write-Host '- Task "'$FoundTask.TaskName'" is unknown... IGNORING' -ForegroundColor yellow } } } If ($CreateTask){ If ($Script:Console){Write-Host '- Creating new scheduled task "'$ActiveTask' "...' -ForegroundColor Cyan } $Script:TaskMessage += '- Creating new scheduled task "'+$ActiveTask+'"...<br>' #--[ Task Parameters ]-------------------- $Principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U -RunLevel Highest $PSArgument = '-WindowStyle Hidden -Noninteractive -noprofile -nologo -executionpolicy Bypass -Command "&{'+$Script:ScriptFullPath+'}"' $Action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $PSArgument $Trigger = @() #--[ Allows creation of multiple triggers ]-- $Trigger += New-ScheduledTaskTrigger -Daily -RandomDelay (New-TimeSpan -Minutes 90) -At $Script:RunTime #--[ Creates patch task with 90 minute random delay ]-- $Trigger += New-ScheduledTaskTrigger -AtStartup $Task = New-ScheduledTask -Action $Action -Trigger $Trigger -Principal $Principal -Settings (New-ScheduledTaskSettingsSet) #--[ Task Parameters ]-------------------- try{ $Result = ($Task | Register-ScheduledTask -TaskName $ActiveTask -Force -ErrorAction Stop ) if ($Script:Console){Write-Host "-- Created"$Result -ForegroundColor green } $Script:TaskMessage += '-- Created"'+$Result+'<br>' }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:TaskMessage += '- Scheduled Task "'+$ActiveTask+'" failed to be created... $ErrorMessage<br>' if ($Script:Console){Write-Host '- Scheduled Task "'$ActiveTask'" failed to be created... $ErrorMessage' -ForegroundColor Red } } }Else{ $Script:TaskMessage += '- Scheduled Task "'+$ActiveTask+'" detected... No Action... <br>' if ($Script:Console){Write-Host '- Scheduled Task "'$ActiveTask'" detected... No Action Required... ' -ForegroundColor Green } } } Function DetectRestart { $OS = Get-WmiObject win32_operatingsystem -ComputerName $Env:ComputerName -ErrorAction SilentlyContinue $LastBoot = [DateTime]$OS.ConvertToDateTime($OS.LastBootUpTime) $TimeNow = (Get-Date) $UpTime = New-TimeSpan -End $TimeNow -Start $LastBoot If (($UpTime.Days -lt 1) -and ($UpTime.Hours -lt 1) -and ($UpTime.Minutes -le 30)){ #--[ If last restart was less than 30 minutes ago start a status run ]-- $Script:Status = $true If ($Script:Console){ Write-Host "- Detected a recent restart..." -ForegroundColor cyan Write-Host "-- Last boot : "$LastBoot -foregroundcolor Yellow Write-Host "-- Uptime : "$UpTime.Days" Days "$UpTime.Hours" Hours "$UpTime.Minutes" Minutes" -ForegroundColor Yellow Write-Host '-- Setting "status" mode...' -ForegroundColor Yellow } } If ($UpTimeCheck -and ($UpTime.Days -ge $Script:UpTimeDays)){ #--[ A secondary check. If system restart has exceeded 5 days this optional email may be sent to warn admins ]-- If ($Script:Console){Write-Host `n"--- No reboot has occurred in over "$UpTime.Days" ---..."`n -ForegroundColor red} $Script:MessageBody += "<br><br>WARNING: Computer $Script:ThisComputer has exceeded the reboot window of $Script:UpTimeDays days.<br>Please investigate or disable this feature of the AUTOUPDATE script.<br><br>" } } Function DeployScript{ #--[ Copies this script to all other systems noted in the config file. ]-- if ($Script:Console){Write-Host '--[ Deploying Updated Script to Peer Hosts ]------------' -ForegroundColor Yellow} foreach ($Target in $Script:PeerList.Split(",")){ if ($Script:Console){Write-Host `n'--[ Deploying to'($Target.ToUpper())' ]-------------------------' -ForegroundColor Cyan} if ($Target -ne $ThisComputer){ if (Test-Path "\\$Target\c$\scripts\unattendedupdate.ps1"){ if ($Script:Console){Write-Host " -- Existing files found..." -ForegroundColor green try{ Get-ChildItem -Path "\\$Target\c$\scripts\" | where{$_.Name -match "unattendedupdate.*"} | Remove-Item -Force:$true -Confirm:$false }catch{ if ($Script:Console){Write-Host " -- File delete on $Target FAILED..." -ForegroundColor Red} if ($Script:Console){Write-Host " -- Error Message = "$_.Exception.Message} if ($Script:Console){Write-Host " -- Error Item = "$_.Exception.ItemName} break } } if (!(Test-Path "\\$Target\c$\scripts\unattendedupdate.ps1")){ if ($Script:Console){Write-Host " -- Deletion validated. Files no longer detected..." -ForegroundColor green} }else{ if ($Script:Console){Write-Host " -- Deletion FAILED..." -ForegroundColor red} } try{ Copy-Item -Path $Script:ScriptFullPath -Destination "\\$Target\c$\scripts\" -Force -Confirm:$false Copy-Item -Path ($PSScriptRoot+'\'+$Script:ScriptName.split('.')[0]+'.xml') -Destination "\\$Target\c$\scripts\" -Force -Confirm:$false }catch{ if ($Script:Console){Write-Host " -- File copy to $Target FAILED..." -ForegroundColor Red} if ($Script:Console){Write-Host " -- Error Message = "$_.Exception.Message} if ($Script:Console){Write-Host " -- Error Item = "$_.Exception.ItemName} break } if (Test-Path "\\$Target\c$\scripts\unattendedupdate.ps1"){ if ($Script:Console){Write-Host " -- Verified PS1 copy to $Target..." -ForegroundColor Green} } if (Test-Path "\\$Target\c$\scripts\unattendedupdate.xml"){ if ($Script:Console){Write-Host " -- Verified XML copy to $Target..." -ForegroundColor Green} } } }else{ if ($Script:Console){Write-Host ' -- Bypassing local system'($Target.ToUpper()) -ForegroundColor yellow} } } if ($Script:Console){Write-Host `n"--- COMPLETED ---" -ForegroundColor Red } break } #==[ End of Functions / Start of Main Process ]=============================================== if ($Script:Console){Write-Host `n"--[ Beginning Run ]-----------------------------------`n" -ForegroundColor cyan } LoadConfig #--[ Load the external config file ]-- if ($Script:Deploy){DeployScript} #--[ Check for a script deployment command, then exit ]-- ScheduledTask "UnattendedUpdate-$Script:ScriptVer" #--[ Check for existance of scheduled task named for current version, create if missing ]-- DetectRestart #--[ Check for last restart to determin RUN or STATUS mode ]-- #--[ Create header for html output file ]-- $Script:MessageBody += ' <style Type="text/css"> table.myTable { border:5px solid black;border-collapse:collapse; } table.myTable td { border:2px solid black;padding:5px} table.myTable th { border:2px solid black;padding:5px;background: #949494 } table.bottomBorder { border-collapse:collapse; } table.bottomBorder td, table.bottomBorder th { border-bottom:1px dotted black;padding:5px; } tr.noBorder td {border: 0; } </style>' $Script:MessageBody += '<table class="myTable"> <tr class="noBorder"><td colspan=5><center><h1>- ' + $Script:eMailSubject + ' -</h1></td></tr> <tr class="noBorder"><td colspan=5><center>The following report displays all recently installed patches on the system.</center></td></tr> <tr class="noBorder"><td colspan=5></tr> <tr class="noBorder"><td colspan=5>- Script Executed by: ' + $Script:UserContext.Name + '</tr> <tr class="noBorder"><td colspan=5>- Script Version : ' + $Script:ScriptVer + '</tr><br> ' $Script:MessageBody += $Script:TaskMessage if ($Script:Status){ if ($Script:Console){Write-Host "- Collecting results..." -ForegroundColor Cyan } $Script:MessageBody += '<tr class="noBorder"><td colspan=5>- Collecting results....</tr><br>' GetResults }elseif (($Script:Manual) -or ($Script:RunDays -Match $Today)){ #--[ Update routine is governed by the week days noted in the config file ]-- if(Test-Path $PSScriptRoot\Results.csv){Remove-Item -path $PSScriptRoot\Results.csv -confirm:$false } if ($Script:Console){Write-Host "- Running update routine..." -ForegroundColor Cyan } $Script:MessageBody += "- Running update routine....<br>" PatchIt }else{ if ($Script:Console){Write-Host "`n-- Nothing scheduled for today --`n" -ForegroundColor Cyan } } if ($Script:Console){Write-Host "`n--- COMPLETED ---" -ForegroundColor Red } <#================================================================================================== #--[ Sample XML config file. Should use the same name as this script and be in the same folder. ]-- <!-- Settings & configuration file --> <Settings> <General> <ReportName>Patch Processing</ReportName> <PeerList>server1,server2,server3,server4</PeerList> <RunDays>Monday,Wednesday,Friday</RunDays> <RunTime>1am</RunTime> <UpTimeDays>5</UpTimeDays> <UpdateSource></UpdateSource> <!-- Set to wsus for internal. anything else for internet --> </General> <Email> <from>UnattendedUpdate@domain.com</from> <To>you@domain.com</To> <Debug>you@domain.com</Debug> <Subject>Automated Patch Processing</Subject> <HTML>$true</HTML> <SmtpServer>10.10.10.1</SmtpServer> </Email> <Credentials> <UserName>domain\serviceaccount</UserName> <Password>76492d1ws656ertg116743a534AGIATbuTeJ7I7MAAwADhH087hnA25MgB8AHIAegB2AHUTGYT6ghjYAZQAxAGQAZANgBiADA8gBaAHwAYwAzADQANgA0AGEAANwBkADEANAA4AGQAZgA3ADIAYQAwA8gBaAHwAYwAzADQANgA0AGEADYAZAA3AGUAZgBkAGYAZAA=</Password> <Key>kdhCO+HCvL87nsdXN0E6/AWnHhQAZgB7812qh7IObie8mE=</Key> </Credentials> </Settings> #> # ServicePowershell 08-15-16 # SIG # Begin signature block # MIINBgYJKoZIhvcNAQcCoIIM9zCCDPMCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUGZJLeES+ueNioGIDmP79B/vU # Gg6gggprMIIFGzCCBAOgAwIBAgITUwAAABOq+DK5n7rn9AACAAAAEzANBgkqhkiG # 9w0BAQsFADAjMSEwHwYDVQQDExhCYW5rIE9mIFN0b2NrdG9uIFJvb3QgQ0EwHhcN # MTUwNTE3MjI0NzAwWhcNMjUwNTE3MjI1NzAwWjBNMRMwEQYKCZImiZPyLGQBGRYD # aW50MR4wHAYKCZImiZPyLGQBGRYOYmFua29mc3RvY2t0b24xFjAUBgNVBAMTDUJP # Uy1IUS1QS0lDQTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXmOf2 # kczIFtxr9BGcRPU2sx/TTOUW5bspdNFYZsPC1/uykWnRRKuQfOxvC3YX/p1Rd2fi # 9nydFX2pBs+e3UI0PiTVtNvMV/srqOYB3DqsinCwmN4TKC+AUDJADnU1nArZ9eYh # d6SM7KA6WA+xDBxJMXa41qpKz7We3a+iteM40zGL49IJiXk9wx4ULbu0OerENJ1W # uo6ijUQVW6jX2JHKiaTry8yszJKHsui9mx8ybramwUr4CLgI6/waPWT6JCaQ8oFX # WafSamHGgRZSUHlNKs9zJrwO8Pzbi48fU6vTtyDa3tXY3yueCsyh7vK0z5ce+HBA # F6+WMU7v5jZ8kWZ9AgMBAAGjggIcMIICGDASBgkrBgEEAYI3FQEEBQIDCAAIMCMG # CSsGAQQBgjcVAgQWBBSu5N3uULjF0GgpSYDnIC85HqAuKjAdBgNVHQ4EFgQUtIGu # 2jKkuNy9LSlnwiT0Uv3e1hQwgZcGA1UdIASBjzCBjDCBiQYLKwYBBAGC0iYBAQow # ejA6BggrBgEFBQcCAjAuHiwATABlAGcAYQBsACAAUABvAGwAaQBjAHkAIABTAHQA # YQB0AGUAbQBlAG4AdDA8BggrBgEFBQcCARYwaHR0cDovL3BraS5iYW5rb2ZzdG9j # a3Rvbi5pbnQvY2VydGRhdGEvY3BzLmh0bWwAMBkGCSsGAQQBgjcUAgQMHgoAUwB1 # AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA # FCT813aKtz1xfcLXpeT2b53u2NPlMFcGA1UdHwRQME4wTKBKoEiGRmh0dHA6Ly9w # a2kuYmFua29mc3RvY2t0b24uaW50L2NlcnRkYXRhL0JhbmsgT2YgU3RvY2t0b24g # Um9vdCBDQSgyKS5jcmwwcQYIKwYBBQUHAQEEZTBjMGEGCCsGAQUFBzAChlVodHRw # Oi8vcGtpLmJhbmtvZnN0b2NrdG9uLmludC9jZXJ0ZGF0YS9CT1MtSFEtUEtJUk9P # VF9CYW5rIE9mIFN0b2NrdG9uIFJvb3QgQ0EoMikuY3J0MA0GCSqGSIb3DQEBCwUA # A4IBAQCq1cc5uFuZH1q7TjJXeMXwa2XJq7vc/5S50qsIJtn1s3Rb1ih5mjRs9cSH # dD83FeSZifE+axjAIFu8XrsGO5J3CR8evcFPTe9ya/1+lnjELJuNRdkzdp3ztmy0 # UvwxQ0wnxH06TxiBkKHUykjrcjX8O2Zq5c1vAsPS4J99jLeibPZV5B54tfif1MlM # 6O2Ld9/A009Ii6giKatmrLB/5wwcX01EB7zq3vsGktvbOlwZEyAaCKujoVVFi79R # VagdW8Z5AbqrPBqO4EsoEIxP7HRoKnPkbtWUfPWm6m5t+ioiREtyQyAwvBtgJTK8 # 0WeGO6hogLyWAvjWrTtDiCU3h/NIMIIFSDCCBDCgAwIBAgITeQABmzTUWW04vlFd # VAAIAAGbNDANBgkqhkiG9w0BAQsFADBNMRMwEQYKCZImiZPyLGQBGRYDaW50MR4w # HAYKCZImiZPyLGQBGRYOYmFua29mc3RvY2t0b24xFjAUBgNVBAMTDUJPUy1IUS1Q # S0lDQTEwHhcNMTYwODE1MTgyMDA4WhcNMTkwODE1MTgyMDA4WjBsMRMwEQYKCZIm # iZPyLGQBGRYDaW50MR4wHAYKCZImiZPyLGQBGRYOYmFua29mc3RvY2t0b24xGDAW # BgNVBAsTD1NlcnZpY2VBY2NvdW50czEbMBkGA1UEAxMSU2VydmljZSBQb3dlclNo # ZWxsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuEkMkkI20j1tg0i9 # wHmgMSs/a37dCxtG16ZrUF3MPzUhnQoToD+APEU1Cm7PeB72av93hJ52/vNL0/vO # SuyMRCASUHbfOXffncN3co3ePVTx8+sp2t2P6gudNoUMt3/pm2I/F39XmVA1sHot # QBBlcBYMPeQCEqNvsCHXpBg0PC1wB3CneY7/0dsCACBICoOQtoMYrQ9Gg6eduPKd # Q32BEprEN2DY4tdWIPn9cUYw7+4dhCoQMXYtGFmRCLjpvAtNYJ2ltofI9/w34NUx # fW5oZ5CzyP+jG/O7tIs98usUEibQnGMo4yc28MzTRF6XaKrfSQQoqis+w+7pWFs5 # ceYegQIDAQABo4ICADCCAfwwPAYJKwYBBAGCNxUHBC8wLQYlKwYBBAGCNxUIg8vb # N6q6aYWhkSmH+K5QhYfTajuE0ahHhJj8BgIBZAIBBzATBgNVHSUEDDAKBggrBgEF # BQcDAzAOBgNVHQ8BAf8EBAMCB4AwGwYJKwYBBAGCNxUKBA4wDDAKBggrBgEFBQcD # AzAdBgNVHQ4EFgQUUbO9DIX0q6kjmYOS3tOnkGoWGKowHwYDVR0jBBgwFoAUtIGu # 2jKkuNy9LSlnwiT0Uv3e1hQwTAYDVR0fBEUwQzBBoD+gPYY7aHR0cDovL3BraS5i # YW5rb2ZzdG9ja3Rvbi5pbnQvY2VydGRhdGEvQk9TLUhRLVBLSUNBMSg4KS5jcmww # gaoGCCsGAQUFBwEBBIGdMIGaMGgGCCsGAQUFBzAChlxodHRwOi8vcGtpLmJhbmtv # ZnN0b2NrdG9uLmludC9jZXJ0ZGF0YS9CT1MtSFEtUEtJQ0ExLmJhbmtvZnN0b2Nr # dG9uLmludF9CT1MtSFEtUEtJQ0ExKDgpLmNydDAuBggrBgEFBQcwAYYiaHR0cDov # L3BraS5iYW5rb2ZzdG9ja3Rvbi5pbnQvb2NzcDA/BgNVHREEODA2oDQGCisGAQQB # gjcUAgOgJgwkU2VydmljZVBvd2VyU2hlbGxAYmFua29mc3RvY2t0b24uaW50MA0G # CSqGSIb3DQEBCwUAA4IBAQBgCe16/qJEt35vWmMXpDqhO6B9KrqN35kvUP2d8Jrb # mzf7UUimG2WjjsU6OH0JkNI5mue5NwuUpoC4Wc3CejB7NJsX18NzQHTquBOeyPpn # E9L85TpxfBud8RM/Tqwg9Eu2UgXP3roHMwOlqokjNktT+74WCJjkUlPlMpo90HG8 # K2HVc46nNOQx5Bhl4I4EPCSLVYWNzfcZbjX/C4TuUo4U7XIiSe29qpamUJ08kv2x # tk8IaBdT47ExF2vTWcaLkloMfgaf62/cLkgOg5xyUafhLd9KUvOX51sLq4UANmY9 # DlDcvVtTfWGth5m5Xb5+dyYDs7GnrTBdNKlMnVqo0D58MYICBTCCAgECAQEwZDBN # MRMwEQYKCZImiZPyLGQBGRYDaW50MR4wHAYKCZImiZPyLGQBGRYOYmFua29mc3Rv # Y2t0b24xFjAUBgNVBAMTDUJPUy1IUS1QS0lDQTECE3kAAZs01FltOL5RXVQACAAB # mzQwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI # hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC # ARUwIwYJKoZIhvcNAQkEMRYEFLlEspLbayWsCjawxozXPlRe+/aGMA0GCSqGSIb3 # DQEBAQUABIIBACBuWMDq1oaYGoIllckmRL+gy6n6XGkaSP60eZJq5JzBX2NaPrZy # 1ajMi6SSI0F/FSip9l64dTylyuO/z77rIsbVkGiEtDet6NzxYLILRVDtDtRMBzGy # 1+szmlUnfJo2dezgqq74NfmE51fQPNCVmvYVE3vAsP+s0AUdYoIC5lY6nfUd9uac # MIOnEgIHR4G6ISHbNRbXTFh23zv5LUQ0VS+Q0TJpwvms/Mfn64YUzUYVhCqtL4cc # NoTJ2Cqb7/I++OLRr9Ir6cMYWRFW3kz8zMz4cRMNkrzqjW+QtzdU6mcQn1lqJIez # ZtbeWPPhHS2WRn2oYBrRUEYeOBi8Zw4250w= # SIG # End signature block |