Testing WSUS server operational status

During the summer, someone asked the following questionwsus-monitoring-question on the WSUS patchmanagement.org mailing list.

I replied and immediately thought that Pester would do the job and quickly showed how he could test if he can connect to the console.

I’ve actually more than a WSUS server to manage. So, I started separating the environmental configuration data from the pester tests code almost the same way Mike F. Robbins did in his recent post where he goes far beyond to what I did.

I think it’s a great idea and here’s what I did in my case to monitor my WSUS server operational status.

To get started, I copied the Pester module on my WSUS server, imported the module and did in the ISE:

# helper to create the required files and folder if not present
New-Fixture -Path  ~/Documents/Pester -Name Test-WSUS
# Put the config data into that file:
psEdit ~/Documents/Pester/Test-WSUS.ps1
# Put the pester code into that file:
psEdit ~/Documents/Pester/Test-WSUS.Tests.ps1

After the first 3 commands, here’s what the ISE console looked liked
pester-new-fixture-wsus-tests

The first file Test-WSUS.ps1 looks like this by default.
It will be used to store my configuration data.
pester-wsus-tests-01

The second file Test-WSUS.Tests.ps1 is where I’ll write the pester tests code
pester-wsus-tests-02

After editing the 1rst file Test-WSUS.ps1 like this:
pester-test-wsus-file (fake data in this case)

…and the 2nd file Test-WSUS.Tests.ps1 like this:

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"
Describe 'Testing WSUS server locally' {
Context 'Windows Features' {
BeforeAll {
$WindowsFeatures = Get-WindowsFeature
}
# 'Web-Server',
#'Web-WebServer',
# 'Web-Common-Http',
'Web-Default-Doc',
'Web-Static-Content',
# 'Web-Performance',
'Web-Dyn-Compression',
# 'Web-Security',
'Web-Filtering',
'Web-Windows-Auth',
#'Web-App-Dev',
'Web-Net-Ext45',
'Web-Asp-Net45',
'Web-ISAPI-Ext',
'Web-ISAPI-Filter',
# 'Web-Mgmt-Tools',
'Web-Mgmt-Console',
# 'Web-Mgmt-Compat',
'Web-Metabase',
# 'UpdateServices',
'UpdateServices-WidDB',
'UpdateServices-Services',
# 'NET-Framework-45-Features',
'NET-Framework-45-Core',
'NET-Framework-45-ASPNET',
# 'NET-WCF-Services45',
'NET-WCF-TCP-PortSharing45',
#'RSAT-Role-Tools',
#'UpdateServices-RSAT',
'UpdateServices-API',
'UpdateServices-UI',
#'User-Interfaces-Infra',
'Server-Gui-Mgmt-Infra',
'Windows-Internal-Database',
#'PowerShellRoot',
'PowerShell',
#'PowerShell-ISE',
#'WAS',
'WAS-Config-APIs',
'WoW64-Support' | ForEach-Object {
$name = $_
It "Feature $($_) should be Installed" {
($WindowsFeatures | Where Name -eq $name).Installed | Should be $true
}
}
}
Context 'IIS' {
BeforeAll {
Import-Module Webadministration -ErrorAction SilentlyContinue
}
It 'Certiticate should be imported in the Cert:\localmachine\My' {
Test-Path -Path "Cert:\LocalMachine\my\$($ThumbPrint)" | Should Be $true
}
It 'Certificate should be valid' {
(dir -Path "Cert:\LocalMachine\my\$($ThumbPrint)").NotAfter.toString('dd/MM/yyyy HH:mm') | Should Be $CertExpiryDate
}
It 'Certiticate should be present in IIS' {
(dir -Path IIS:\SslBindings\0.0.0.0!$($Port)).Thumbprint | should be "$($ThumbPrint)"
}
}
Context 'Services' {
It 'the WsusService service is in Automatic start mode' {
(Get-WmiObject -Query "Select * FROM Win32_service WHERE Name = 'WsusService'").StartMode | Should Be 'Auto'
}
It 'the WsusService service is running' {
(Get-Service -Name WsusService).Status | Should Be 'Running'
}
}
Context 'Firewall' {
Get-NetFirewallProfile | ForEach-Object {
It "Firewall profile $($_.Name) is Enabled" {
$_.Enabled | Should be $true
}
}
Get-NetFirewallRule -Name 'IIS-WebServerRole-HTTPS-In-TCP' | ForEach-Object {
It "Firewall rule $($_.Name) should be Enabled" {
$_.Enabled | Should be $true
}
It "Firewall rule $($_.Name) action should be Allow" {
$_.Action | Should be 'Allow'
}
}
Get-NetFirewallRule -Name 'IIS-WebServerRole-HTTP-In-TCP' | ForEach-Object {
It "Firewall rule $($_.Name) should be Enabled" {
$_.Enabled | Should be $true
}
It "Firewall rule $($_.Name) action should be Allow" {
$_.Action | Should be 'Allow'
}
}
}
Context 'Console connection' {
BeforeAll {
Add-Type -Path "$($env:ProgramFiles)\Update Services\Api\Microsoft.UpdateServices.Administration.dll"
}
It 'should connect to the console' {
{ [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer("$($FQDN)",$true,$Port) } | Should Not Throw
}
}
Context 'Configuration' {
BeforeAll {
$WSUSServer = Get-WsusServer
$DB = (Get-WsusServer).GetDatabaseConfiguration()
$WSUSConfig = (Get-WsusServer).GetConfiguration()
}
It 'Shoule be ready' {
((Get-WsusServer).GetConfiguration().GetUpdateServerConfigurationState()).value__ | Should be '0'
}
It 'BypassApiRemoting should be False' {
$WSUSServer.BypassApiRemoting | Should be $false
}
It 'IsConnectionSecureForApiRemoting should be True' {
$WSUSServer.IsConnectionSecureForApiRemoting | Should be $true
}
It 'IsServerLocal should be True' {
$WSUSServer.IsServerLocal | Should be $true
}
It "PortNumber should be $($Port)" {
$WSUSServer.PortNumber | Should be "$($Port)"
}
It 'PreferredCulture should be en' {
$WSUSServer.PreferredCulture | Should be 'en'
}
It "ServerName should be $($FQDN)" {
$WSUSServer.ServerName | Should be "$($FQDN)"
}
It 'UseSecureConnection should be True' {
$WSUSServer.UseSecureConnection | Should be 'True'
}
It "WebServiceUrl should be https://$($FQDN):$($Port)/ApiRemoting30/WebService.asmx" {
$WSUSServer.WebServiceUrl | Should be "https://$($FQDN):$($Port)/ApiRemoting30/WebService.asmx"
}
It 'Should use SUSDB' {
$DB.DatabaseName | Should be 'SUSDB'
}
It 'Should use a DB server named' {
$DB.ServerName | Should be 'MICROSOFT##WID'
}
It 'Should use a DB server named' {
$DB.IsUsingWindowsInternalDatabase | Should be $true
}
It 'Should use the Windows Auth mode' {
$DB.AuthenticationMode | Should be 'WindowsAuthentication'
}
It 'Should have data on D:\WSUS.Content\WsusContent' {
$WSUSConfig.LocalContentCachePath | Should be 'D:\WSUS.Content\WsusContent'
}
It 'Should sync from MU' {
$WSUSConfig.SyncFromMicrosoftUpdate | Should be $true
}
It 'Should use a proxy' {
$WSUSConfig.UseProxy | Should be $true
}
It "Proxy Should be set to $($Proxy)" {
$WSUSConfig.ProxyName | Should be "$($Proxy)"
}
It "Proxy port Should be set to $($ProxyPort)" {
$WSUSConfig.ProxyServerPort | Should be "$($ProxyPort)"
}
It 'Proxy use same proxy for SSL ' {
$WSUSConfig.UseSeparateProxyForSsl | Should be $false
}
It 'Should be targeting clients using Groups and GPO' {
$WSUSConfig.TargetingMode | Should be 'Client'
}
}
Context 'Products' {
BeforeAll {
$Categories = (Get-WsusServer).GetSubscription().GetUpdateCategories()
}
It 'Should have the Office 2010 product' {
($Categories| Where Id -eq '84f5f325-30d7-41c4-81d1-87a0e6535b66').Title | Should be 'Office 2010'
}
It 'Should have the Windows 7 product' {
($Categories| Where Id -eq 'bfe5b177-a086-47a0-b102-097e4fa1f807').Title | Should be 'Windows 7'
}
It 'Should have the Windows Defender product' {
($Categories| Where Id -eq '8c3fcc84-7410-4a95-8b89-a166a0190486').Title | Should be 'Windows Defender'
}
}
Context 'Classification' {
BeforeAll {
$Classifications = (Get-WsusServer).GetSubscription().GetUpdateClassifications()
}
It 'Should have enabled the Critical Updates' {
($Classifications | Where Id -eq 'e6cf1350-c01b-414d-a61f-263d14d133b4').Title | Should be 'Critical Updates'
}
It 'Should have enabled the Definition Updates' {
($Classifications | Where Id -eq 'e0789628-ce08-4437-be74-2495b842f43b').Title | Should be 'Definition Updates'
}
It 'Should have enabled the Feature Packs' {
($Classifications | Where Id -eq 'b54e7d24-7add-428f-8b75-90a396fa584f').Title | Should be 'Feature Packs'
}
It 'Should have enabled the Security Updates' {
($Classifications | Where Id -eq '0fa1201d-4330-4fa8-8ae9-b877473b6441').Title | Should be 'Security Updates'
}
It 'Should have enabled the Service Packs' {
($Classifications | Where Id -eq '68c5b0a3-d1a6-4553-ae49-01d3a7827828').Title | Should be 'Service Packs'
}
It 'Should have enabled the Update Rollups' {
($Classifications | Where Id -eq '28bc880e-0592-4cbf-8f95-c79b17911d5f').Title | Should be 'Update Rollups'
}
It 'Should have enabled the Updates' {
($Classifications | Where Id -eq 'cd5ffd1e-e932-4e3a-bf74-18bf0b1bbd83').Title | Should be 'Updates'
}
}
Context 'Languages' {
It 'should only sync English language' {
(Get-WsusServer).GetConfiguration().GetEnabledUpdateLanguages() | Should be 'en'
}
}
Context 'Sync schedule' {
It 'Should sync manually' {
(Get-WsusServer).GetSubscription().SynchronizeAutomatically | Should be $false
}
}
Context 'Auto approval rules' {
It 'Should not have automatic approval rules' {
((Get-WsusServer).GetInstallApprovalRules() | Where Name -eq 'Default Automatic Approval Rule').Enabled | Should be $false
}
}
Context 'Computer target groups' {
It 'Should have a Windows 7 x64 target group' {
((Get-WsusServer).GetComputerTargetGroups() | Where Name -eq 'Windows 7 x64').Name | should be 'Windows 7 x64'
}
}
}

…I’m actually ready to assess the operational readiness of my WSUS configuration by using the following cmdlet:

Invoke-Pester -Script ~/Documents/Pester/Test-WSUS.Tests.ps1

wsus-server-config-invoke-pester

I still feel like a Pester newbie but no doubt that Pester rocks 😎

3 thoughts on “Testing WSUS server operational status

    • You’re right in all the above cases, it’s not necessary.
      $() is called an expression or “sub-expression”. Everything is an object in PowerShell including strings and variables.
      It’s actually a habit that enables me to do (anything) things like:
      $($list.Length)
      $($values[$i])
      $(Get-Content File1.txt)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.