If you have enabled RemotePowershell on your Server you can retrieve the Information via PowerShell, too.
Create a new .ps1-file as Custom-Exe Sensor at '<PRTG-Folder>\Custom Sensors\EXE' with the following Content:
param([Parameter(Mandatory=$true)][string] $Computer,
[Parameter(Mandatory=$true)][string] $User,
[Parameter(Mandatory=$true)][string] $Password,
[switch] $PSRemoting
)
#############################
# this 'sensor' is a proof of concept to retrieve information about the last windows-update via PowerShell
# it is based on the work of http://www.powershelladmin.com/wiki/Check_when_servers_were_last_patched_with_Windows_Update_via_COM_or_WSUS
# the only adoption is to
# - authenticate to the remote-machine with given credentials (to be able to run from within PRTG as it is started in the scope of the service-user)
# - scan only one server instead of a list of servers
# - return no html but a PRTG-readable result to be used as a 'custom exe'-sensor
#
# Parameters are '-computer <ip or fqdn> -user <username as domain\username> -password <the SECRET password> -PSRemoting'
# In PRTG set '-computer %host -user %windowsdomain\%windowsuser -password %windowspassword -PSRemoting' to use the settings from the device without making the password public
#
# Result is '<days since last update> : <last update datetime> -> <name of the update last installed>'
#############################
# create a credential-object for authenticating with the given credentials
$secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
$myCred = New-Object System.Management.Automation.PSCredential ($user, $secpasswd)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function ql { $args }
$LastUpdates = @{}
$Errors = @{}
$script:ContinueFlag = $false
if ( -not (Test-Connection -Quiet -Count 1 -ComputerName $Computer) ) {
$Errors.$Computer = 'Error: No ping reply'
}
# Use "local COM" (well, local, but remote via PS) and Invoke-Command if PSRemoting is specified.
if ($PSRemoting) {
try {
$PSSession = New-PSSession -ComputerName $Computer -Authentication Kerberos -Credential $myCred
$Result = Invoke-Command -Session $PSSession -ScriptBlock {
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Update.Session') | Out-Null
$Session = New-Object -ComObject Microsoft.Update.Session
try {
$UpdateSearcher = $Session.CreateUpdateSearcher()
$NumUpdates = $UpdateSearcher.GetTotalHistoryCount()
$InstalledUpdates = $UpdateSearcher.QueryHistory(1, $NumUpdates)
if ($?) {
$LastInstalledUpdate = $InstalledUpdates | Select-Object Title, Date | Sort-Object -Property Date -Descending | Select-Object -first 1
# Return a collection/array. Later it is assumed that an array type indicates success.
# Errors are of the class [System.String]. -- Well, that didn't work so well in retrospect.
$LastInstalledUpdate.Title, $LastInstalledUpdate.Date
}
else {
"Error. Win update search query failed: $($Error[0] -replace '[\r\n]+')"
}
} # end of inner try block
catch {
$Errors.$Computer = "Error (terminating): $($Error[0] -replace '[\r\n]+')"
continue
}
} # end of Invoke-Command
} # end of outer try block
# Catch the Invoke-Command errors here
catch {
$Errors.$Computer = "Error with Invoke-Command: $($Error[0] -replace '[\r\n]+')"
}
# $Result here is what's returned from the invoke-command call.
# I can't populate the data hashes inside the Invoke-Command due to variable scoping.
if (-not $Result -is [array]) {
$Errors.$Computer = $Result
}
else {
$Title, $Date = $Result[0,1]
$LastUpdates.$Computer = New-Object PSObject -Property @{
'Title' = $Title
'Date' = $Date
}
}
}
# If -PSRemoting isn't provided as an argument, try remote COM.
else {
try {
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.Update.Session')
$Session = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session", $Computer))
$UpdateSearcher = $Session.CreateUpdateSearcher()
$NumUpdates = $UpdateSearcher.GetTotalHistoryCount()
$InstalledUpdates = $UpdateSearcher.QueryHistory(1, $NumUpdates)
if ($?) {
$LastInstalledUpdate = $InstalledUpdates | Select-Object Title, Date | Sort-Object -Property Date -Descending | Select-Object -first 1
$LastUpdates.$Computer = New-Object PSObject -Property @{
'Title' = $LastInstalledUpdate.Title
'Date' = $LastInstalledUpdate.Date
}
}
else {
$Errors.$Computer = "Error. Win update search query failed: $($Error[0].ToString())"
}
}
catch {
$Errors.$Computer = "Terminating error: $($Error[0].ToString())"
}
}
# exit PSSession as per default there are only 5 concurrent logins per users allowed that will time out after 15 minutes...
Exit-PSSession
Remove-PSSession -session $PSsession
if ( $Errors.Keys.count -ne 0 ) {
# replacing ':' in the error-message to avoid 'Response not wellformed'-message in PRTG...
write-host 0:$Errors.$Computer -replace ":", "."
exit 1
} else {
$resultValue = $([int]$($(get-date) - $LastUpdates.$Computer.Date).TotalDays)
# replacing ':' in the time-string as well as in the Title of the update to avoid 'Response not wellformed'-message in PRTG...
$resultText = "$($LastUpdates.$Computer.Date.toString()) -> $($LastUpdates.$Computer.Title)" -replace ":", "."
write-host $resultValue":" $resultText
exit 0
}
See comment inside the script for using the parameter-settings in PRTG.
Kind Regards
Add comment