Created
June 7, 2019 22:28
-
-
Save Chirishman/0821bdca244e30f1cd2769ca2ee7cac8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[DscResource()] | |
class MaintenancePatching { | |
[DscProperty(Key)] | |
[string]$Name | |
[DscProperty()] | |
[string]$MaintenanceWindow | |
[DscProperty()] | |
[boolean]$SingleInstance | |
[DscProperty()] | |
[ValidateSet("Security","Important","Optional")] | |
[String[]]$Category | |
[DscProperty()] | |
[ValidateSet("Disabled","ScheduledInstallation")] | |
[String]$NotificationSettings | |
[DscProperty(NotConfigurable)] | |
[string]$AutomaticUpdatesNotificationSetting | |
[DscProperty(NotConfigurable)] | |
[string]$CurrentUpdatesNotificationSetting | |
[DscProperty()] | |
[ValidateSet("WindowsUpdate","MicrosoftUpdate","WSUS")] | |
[String]$UpdateSource | |
[DscProperty(NotConfigurable)] | |
[String]$Source | |
[DscProperty()] | |
[boolean]$SkipCcmClientSDK | |
[DscProperty(NotConfigurable)] | |
[boolean]$CcmClientSDK | |
[DscProperty(NotConfigurable)] | |
[int]$PendingUpdates = 0 | |
[DscProperty(NotConfigurable)] | |
[string]$SearchString | |
#TODO figure out why the hell this breaks | |
<# Objects of either of these types run fine when the definition is run interactively but both fail when importing/using in a configuration. A mystery for some time that isn't 10:30pm on a friday before a long weekend. | |
[DscProperty(NotConfigurable)] | |
[System.__ComObject]$Settings | |
[DscProperty(NotConfigurable)] | |
[System.__ComObject]$Session | |
[DscProperty(NotConfigurable)] | |
[System.__ComObject]$Searcher | |
[DscProperty(NotConfigurable)] | |
[System.__ComObject]$Search | |
[DscProperty(NotConfigurable)] | |
[System.__ComObject]$ServiceManager | |
[DscProperty(NotConfigurable)] | |
[System.__ComObject]$Services | |
[DscProperty(NotConfigurable)] | |
[Object]$DefaultService | |
[DscProperty(NotConfigurable)] | |
[Object]$DownloadResult | |
[DscProperty(NotConfigurable)] | |
[Object]$InstallResult | |
#> | |
[DscProperty(NotConfigurable)] | |
[hashtable]$ObjectHandler | |
[DscProperty(NotConfigurable)] | |
[boolean]$UpdatesFound | |
[DscProperty(NotConfigurable)] | |
[boolean]$notificationCompliant | |
[DscProperty(NotConfigurable)] | |
[boolean]$SourceCompliant | |
hidden [object] GetWuaWrapper([ScriptBlock] $tryBlock) { | |
$ExceptionReturnValue = $null | |
return $( | |
try { | |
$Return = Invoke-Command -ScriptBlock $tryBlock -NoNewScope | |
if ($Return) { | |
$Return | |
} else { | |
$ExceptionReturnValue | |
} | |
} | |
catch [System.Runtime.InteropServices.COMException] | |
{ | |
switch($_.Exception.HResult) | |
{ | |
# 0x8024001e -2145124322 WU_E_SERVICE_STOP Operation did not complete because the service or system was being shut down. wuerror.h | |
-2145124322 { | |
Write-Warning 'Got an error that WU service is stopping. Handling the error.' | |
$ExceptionReturnValue | |
} | |
# 0x8024402c -2145107924 WU_E_PT_WINHTTP_NAME_NOT_RESOLVED Same as ERROR_WINHTTP_NAME_NOT_RESOLVED - the proxy server or target server name cannot be resolved. wuerror.h | |
-2145107924 { | |
# TODO: add retry for this error | |
Write-Warning 'Got an error that WU could not resolve the name of the update service. Handling the error.' | |
$ExceptionReturnValue | |
} | |
# 0x8024401c -2145107940 WU_E_PT_HTTP_STATUS_REQUEST_TIMEOUT Same as HTTP status 408 - the server timed out waiting for the request. wuerror.h | |
-2145107940 { | |
# TODO: add retry for this error | |
Write-Warning 'Got an error a request timed out (http status 408 or equivalent) when WU was communicating with the update service. Handling the error.' | |
$ExceptionReturnValue | |
} | |
# 0x8024402f -2145107921 WU_E_PT_ECP_SUCCEEDED_WITH_ERRORS External cab file processing completed with some errors. wuerror.h | |
-2145107921 { | |
# No retry needed | |
Write-Warning 'Got an error that CAB processing completed with some errors.' | |
$ExceptionReturnValue | |
} | |
default { | |
throw | |
} | |
} | |
} | |
) | |
} | |
hidden [void] InitializeObjectHandler(){ | |
$this.ObjectHandler = @{ | |
Settings = $null | |
Session = $null | |
Searcher = $null | |
Search = $null | |
ServiceManager = $null | |
Services = $null | |
DefaultService = $null | |
DownloadResult = $null | |
InstallResult = $null | |
} | |
} | |
hidden [Object] GetWuaAu() { | |
return (New-Object -ComObject 'Microsoft.Update.AutoUpdate') | |
} | |
hidden [void] GetWuaSession() { | |
$this.ObjectHandler.Session = (New-Object -ComObject 'Microsoft.Update.Session') | |
} | |
hidden [void] GetWuaServiceManager() { | |
$this.ObjectHandler.ServiceManager = (New-Object -ComObject Microsoft.Update.ServiceManager) | |
$this.ObjectHandler.Services = $this.ObjectHandler.ServiceManager.Services | |
$this.ObjectHandler.DefaultService = @($this.ObjectHandler.Services).where{$_.IsDefaultAuService} | |
Write-Verbose -Message "Get default search service: $($this.ObjectHandler.DefaultService.ServiceId)" | |
if($this.ObjectHandler.DefaultService.ServiceId -eq '7971f918-a847-4430-9279-4a52d1efe18d') | |
{ | |
$this.Source = 'MicrosoftUpdate' | |
} | |
elseif ($this.ObjectHandler.DefaultService.IsManaged) { | |
$this.Source = 'WSUS' | |
} else { | |
$this.Source = 'WindowsUpdate' | |
} | |
} | |
hidden [void] GetWuaAuSettings() { | |
$this.ObjectHandler.settings = ($this.GetWuaAu()).Settings | |
} | |
hidden [void] GetWuaSearchString ([bool]$security,[bool]$important,[bool]$optional) { | |
$securityCategoryId = "'0FA1201D-4330-4FA8-8AE9-B877473B6441'" | |
# security and optional and important | |
# not security and optional and important | |
$this.SearchString = $( | |
if($optional -and $important) | |
{ | |
# Installing everything not hidden and not already installed | |
'IsHidden=0 and IsInstalled=0' | |
} | |
# security and optional and not important | |
elseif ($security -and $optional) { | |
# or can only be used at the top most boolean expression | |
"(IsAssigned=0 and IsHidden=0 and IsInstalled=0) or (CategoryIds contains $securityCategoryId and IsHidden=0 and IsInstalled=0)" | |
} | |
# security and not optional and important | |
elseif($security -and $important ){ | |
# Installing everything not hidden, | |
# not optional (optional are not assigned) and not already installed | |
'IsAssigned=1 and IsHidden=0 and IsInstalled=0' | |
} | |
elseif ($optional -and $important) { | |
# Installing everything not hidden, | |
# not optional (optional are not assigned) and not already installed | |
'IsHidden=0 and IsInstalled=0' | |
} | |
# security and not optional and not important | |
elseif ($security) { | |
# Installing everything that is security and not hidden, | |
# and not already installed | |
"CategoryIds contains $securityCategoryId and IsHidden=0 and IsInstalled=0" | |
} | |
# not security and not optional and important | |
elseif ($important) { | |
# Installing everything that is not hidden, | |
# is assigned (not optional) and not already installed | |
# not valid cannot do not contains or a boolean not | |
# Note important updates will include security updates | |
"IsAssigned=1 and IsHidden=0 and IsInstalled=0" | |
} | |
# not security and optional and not important | |
elseif ($optional) { | |
# Installing everything that is not hidden, | |
# is not assigned (is optional) and not already installed | |
# not valid cannot do not contains or a boolean not | |
# Note optional updates may include security updates | |
"IsAssigned=0 and IsHidden=0 and IsInstalled=0" | |
} else { | |
"CategoryIds contains $securityCategoryId and IsHidden=0 and IsInstalled=0" | |
} | |
) | |
} | |
hidden [void] GetWuaSearcher() { | |
$this.GetWuaSearchString(($this.Category -contains 'Security'),($this.Category -contains 'Important'),($this.Category -contains 'Optional')) | |
$this.GetWuaWrapper( | |
{ | |
$this.ObjectHandler.Searcher = $this.ObjectHandler.Session.CreateUpdateSearcher() | |
Write-Verbose -Message "Searching for updating using: $($this.SearchString)" -Verbose | |
$this.ObjectHandler.Search = $this.ObjectHandler.Searcher.Search($this.SearchString) | |
} | |
) | |
} | |
hidden [bool] TestSearchResult() { | |
if(!(@($this.ObjectHandler.Search | get-member |Select-Object -ExpandProperty Name) -contains 'Updates')) | |
{ | |
Write-Verbose 'Did not find updates on SearchResult' | |
return $false | |
} | |
if(!(@(Get-Member -InputObject $this.ObjectHandler.Search.Updates |Select-Object -ExpandProperty Name) -contains 'Count')) | |
{ | |
Write-Verbose 'Did not find count on updates on SearchResult' | |
return $false | |
} | |
return $true | |
} | |
hidden [void] GetWuaAuNotificationLevel() { | |
$this.CurrentUpdatesNotificationSetting = @{ | |
0 = 'Not Configured' | |
1 = 'Disabled' | |
2 = 'Notify before download' | |
3 = 'Notify before installation' | |
4 = 'Scheduled installation' | |
[string]::Empty = 'Reserved' | |
}[$this.ObjectHandler.settings.NotificationLevel] | |
} | |
hidden [void] InvokeWuaDownloadUpdates() { | |
$downloader = $this.ObjectHandler.Session.CreateUpdateDownloader() | |
$downloader.Updates = $this.ObjectHandler.Search.Updates | |
Write-Verbose -Message 'Downloading updates...' -Verbose | |
$This.ObjectHandler.DownloadResult = $downloader.Download() | |
} | |
hidden [void] InvokeWuaInstallUpdates() { | |
$installer = $this.ObjectHandler.Session.CreateUpdateInstaller() | |
$installer.Updates = $this.ObjectHandler.Search.Updates | |
Write-Verbose -Message 'Installing updates...' -Verbose | |
$this.ObjectHandler.InstallResult = $installer.Install() | |
} | |
hidden [int] GetWuaAuNotificationLevelInt() { | |
$intNotificationLevel =0 | |
switch -Regex ($this.AutomaticUpdatesNotificationSetting) { | |
'^Not\s*Configured$' { $intNotificationLevel = 0 } | |
'^Disabled$' { $intNotificationLevel = 1 } | |
'^Notify\s*before\s*download$' { $intNotificationLevel = 2 } | |
'^Notify\s*before\s*installation$' { $intNotificationLevel = 3 } | |
'^Scheduled\s*installation$' { $intNotificationLevel = 4 } | |
default { throw 'Invalid notification level'} | |
} | |
return $intNotificationLevel | |
} | |
hidden [void] SetWuaAuNotificationLevel() { | |
$this.ObjectHandler.settings.NotificationLevel = $this.GetWuaAuNotificationLevelInt() | |
$this.ObjectHandler.settings.Save() | |
} | |
hidden [void] AddWuaService() { | |
[string]$ServiceId = '7971f918-a847-4430-9279-4a52d1efe18d' | |
[int]$Flags = 7 | |
[string]$AuthorizationCabPath = [string]::Empty | |
$this.ObjectHandler.ServiceManager.AddService2($ServiceId, $Flags, $AuthorizationCabPath) | |
} | |
hidden [void] RemoveWuaService() { | |
[string]$ServiceId = '7971f918-a847-4430-9279-4a52d1efe18d' | |
$this.ObjectHandler.ServiceManager.RemoveService($ServiceId) | |
} | |
[MaintenancePatching] Get() { | |
$this.InitializeObjectHandler() | |
$this.GetWuaWrapper({$this.GetWuaAuSettings()}) | |
$this.GetWuaAuNotificationLevel() | |
$this.GetWuaWrapper({$this.GetWuaSession()}) | |
$this.GetWuaSearcher() | |
$this.GetWuaServiceManager() | |
if($this.ObjectHandler.Search -and ($this.TestSearchResult())) | |
{ | |
$this.PendingUpdates = $this.ObjectHandler.Search.Updates.Count | |
} else { | |
$this.PendingUpdates = 0 | |
} | |
$this.UpdatesFound = [bool]$this.PendingUpdates | |
$this.notificationCompliant = (!$this.NotificationSettings -or $this.NotificationSettings -eq $this.CurrentUpdatesNotificationSetting) | |
$this.SourceCompliant = (!$this.UpdateSource -or $this.UpdateSource -eq $this.Source) | |
return $this | |
} | |
Set() { | |
$status = $this.Get() | |
if($status.PendingUpdates){ | |
if($status.ObjectHandler.Search -and $status.ObjectHandler.Search.Updates.Count -gt 0) { | |
Write-Verbose -Verbose -Message 'Installing updates...' | |
#Write Results | |
$status.ObjectHandler.Search.Updates | ForEach-Object { | |
Write-Verbose -Message "Found update: $($_.Title)" -Verbose | |
} | |
$status.InvokeWuaDownloadUpdates() | |
$status.InvokeWuaInstallUpdates() | |
} else { | |
Write-Verbose -Verbose -Message 'No updates' | |
} | |
} | |
if(!$status.notificationCompliant) { | |
Try { | |
#TODO verify that group policy is not overriding this settings | |
# if it is throw an error, if it conflicts | |
$status.SetWuaAuNotificationLevel() | |
} Catch { | |
$ErrorMsg = $_.Exception.Message | |
Write-Verbose $ErrorMsg | |
} | |
} | |
if(!$status.SourceCompliant) { | |
if($status.UpdateSource -eq 'MicrosoftUpdate') { | |
Write-Verbose "Enable the Microsoft Update setting" | |
$status.AddWuaService() | |
Restart-Service wuauserv -ErrorAction SilentlyContinue | |
} elseif($status.UpdateSource -eq 'WindowsUpdate') { | |
Write-Verbose "Disable the Microsoft Update setting" | |
$status.RemoveWuaService() | |
} | |
} | |
} | |
[bool] Test() { | |
$status = $this.Get() | |
#TODO Add a modified version of Test-TargetResourceProperties here for verbose parameter validation | |
Write-Verbose "Updates Found: $($status.UpdatesFound)" | |
Write-Verbose "notifications compliant: $($status.notificationCompliant)" | |
Write-Verbose "service compliant: $($status.SourceCompliant)" | |
If(!$status.UpdatesFound -and $status.notificationCompliant -and $status.SourceCompliant) { | |
Write-Verbose 'No pending updates found.' | |
return $true | |
} | |
Else { | |
Write-Verbose 'Checking Window' | |
$window = ([MaintenanceWindow]$status.MaintenanceWindow).CheckWindow() | |
Write-Verbose "Window: $window" | |
return $window | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment