Working with WSUS, I sometimes find myself declining the exact same type of updates each month after Patch Tuesday. Not surprisingly, a common trend of those updates involve having the word ‘’Itanium” somewhere in the update title. Instead of having to mess with clicking through the UI and finding all of these annoying updates, and as there are no automated declining rules available in WSUS, I think a more automated approach is necessary using the APIs and PowerShell.
Honestly, there really isn’t a whole lot of code required to make this happen. The key piece is that you need to have the WSUS Administrator console installed (for Win2K8R2 and below) or the UpdateServices module available (Win2K12 and above).
I will break down the code below and at the very end have all of it available to copy to create. My idea behind this is to just make a scheduled job that can be used to run every second Wednesday (after Patch Tuesday) to automatically decline all of these unneeded updates.
Param ( [string]$UpdateServer = 'DC1', [int]$Port = 80, [bool]$Secure = $False )
This is my main parameters that are configurable based on your environment. Here I decide server I will connect to, the port that is open on the server and if the connection is secure.
If (-Not (Import-Module UpdateServices -PassThru)) { Add-Type -Path ` "$Env:ProgramFiles\Update Services\Api\Microsoft.UpdateServices.Administration.dll" -PassThru }
A check is made for the UpdateServices module by actually attempting to import the module. I call –Passthru so an object is outputted which can then be used in the If statement. If no object is outputted, then it is assumed that the module doesn’t exist, the OS doesn’t support the module, etc… and then attempts to load the assembly from a known good location.
$Wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::` GetUpdateServer($UpdateServer,$Secure,$Port)
Finally, a connection is made to the WSUS server using the parameters specified.
$approveState = 'Microsoft.UpdateServices.Administration.ApprovedStates' -as [type] $updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope -Property @{ TextIncludes = 'itanium' ApprovedStates = $approveState::NotApproved, $approveState::LatestRevisionApproved, $approveState::HasStaleUpdateApprovals }
Here I begin to piece together the filter that will be used with the query for updates. I create an object that holds the ApprovedStates enumeration which will make it easier to find what I can use with the creation of the UpdateScope object. Speaking of the UpdateScope object, this is the filtering piece that I will include with my query. I make sure that I specify itanium as for the TextIncludes propery as well as the ApprovedStates. Because I want to make sure that all non-approved and all non-declined updates have been found, I add the necessary ApprovedStates to the object.
$wsus.GetUpdates($updateScope) | ForEach { Write-Verbose ("Declining {0}" -f $_.Title) -Verbose $_.Decline() }
Lastly, I can now finally begin declining all of these updates that have been found by my query. Fortunately, all I need to do is call the Decline() method associated with each update object.
You can download the source code for this below and modify it to suit your needs before deploying it in your environment as a scheduled job!
In my case, I will use PowerShell to create my scheduled job.
$triggerParam = @{ At = "12:00 AM" DaysOfWeek = 'Wednesday' WeeksInterval = 1 Weekly = $True } $trigger = New-JobTrigger @triggerParam $JobParam = @{ Trigger = $trigger FilePath = 'C:\DeclineItanium.ps1' Name = 'DeclineItaniumUpdates' } Register-ScheduledJob @JobParam
Source Code
Param ( [string]$UpdateServer = 'DC1', [int]$Port = 80, [bool]$Secure = $False ) If (-Not (Import-Module UpdateServices -PassThru)) { Add-Type -Path "$Env:ProgramFiles\Update Services\Api\Microsoft.UpdateServices.Administration.dll" -PassThru } $Wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::` GetUpdateServer($UpdateServer,$Secure,$Port) $approveState = 'Microsoft.UpdateServices.Administration.ApprovedStates' -as [type] $updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope -Property @{ TextIncludes = 'itanium' ApprovedStates = $approveState::NotApproved, $approveState::LatestRevisionApproved, $approveState::HasStaleUpdateApprovals } $wsus.GetUpdates($updateScope) | ForEach { Write-Verbose ("Declining {0}" -f $_.Title) -Verbose $_.Decline() }
Would something of changed in Server 2012 R2 for this to not work? I have used the computer name and localhost but both fail:
Exception calling “GetUpdateServer” with “3” argument(s): “The request failed with HTTP status 404: Not Found.”
At C:\Scripts\Decline-WsusItaniumUpdates-2.ps1:11 char:1
+ $Wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::`
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebException
You cannot call a method on a null-valued expression.
At C:\Scripts\Decline-WsusItaniumUpdates-2.ps1:23 char:1
+ $wsus.GetUpdates($updateScope) | ForEach {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Don’t forget that some of the Itanium updates are labeled as “IA64”, not Itanium.
hi,
how do you add more text parameters?
how can you use the text parameter to search for more than one word?
Pingback: Installing and Configuring WSUS with Powershell | smsagent
Very Nice!!! Thank you so much, this is a time saver for me at the job!
My WSUS server is on its last legs and keeps timing out, but this is the code I’m hoping to eventually use:
[Cmdletbinding(SupportsShouldProcess=$True)]
Param (
[string]$UpdateServer = ‘jdhit-dc01’,
[int]$Port = 80,
[string]$Text=”itanium”,
[bool]$Secure = $False
)
If (-Not (Import-Module UpdateServices -PassThru -errorAction SilentlyContinue)) {
Write-Verbose “Adding WSUS DLL”
Try {
Add-Type -Path “$Env:ProgramFiles\Update Services\Api\Microsoft.UpdateServices.Administration.dll” -ErrorAction Stop
}
Catch {
Write-Warning “Failed to import or load Update Services”
#bail out
Return
}
}
Write-Verbose “Connecting to $Updateserver”
$Wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer($UpdateServer,$Secure,$Port)
$approveState = ‘Microsoft.UpdateServices.Administration.ApprovedStates’ -as [type]
Write-Verbose “Filtering for updates where text includes $text”
$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope -Property @{
TextIncludes = $Text
ApprovedStates = $approveState::NotApproved,
$approveState::LatestRevisionApproved,
$approveState::HasStaleUpdateApprovals
}
Write-Verbose “Getting updates”
$wsus.GetUpdates($updateScope) | ForEach {
Write-Verbose (“Declining {0}” -f $_.Title) -Verbose
if ($pscmdlet.ShouldProcess($_.title)) {
$_.Decline()
}
}
This is awesome. I tweaked your code to make the text a parameter so that I can also decline things like XP updates. I also added support for -WhatIf which I think is important whenever we have a script or function that deletes things.
Thanks, Jeff!
The only reason why I left out the -WhatIf support is because I had this as scheduled job in mind to ‘set and forget’. But you are right, it is always good practice to throw in WhatIf support for anything that performs some sort of action such as delete in a script/function.
I’d love to see your finished script with the extra params and such!