Skip to content

Instantly share code, notes, and snippets.

@Chirishman
Created June 7, 2019 22:28
Show Gist options
  • Save Chirishman/0821bdca244e30f1cd2769ca2ee7cac8 to your computer and use it in GitHub Desktop.
Save Chirishman/0821bdca244e30f1cd2769ca2ee7cac8 to your computer and use it in GitHub Desktop.
[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