May 16, 2009

Run MBSA against all machines in WSUS

As a WSUS administrator, I often find the various views and reports frustrating.

Say you have a number of machines, which you want to see the status of from a OS security perspective, but you are not interested in the various other MS products (office, Exchange etc.) – No joy.

WSUS will show the machines overall status including everything. Handing report based on that, over to others will surely guarantee that all sorts of time will have to spent explaining all sorts of stuff.

Now, I’m sure that some may say that all the information can be gathered from the SQL database in one form or another, in that case, please let me know :-)

From another perspective, the WSUS also acts like a good database of machines in scope. It provides various key information about the environment, which can surely be used for something..

The result of the above was a powershell script to get all the machines in WSUS and perform an MBSA scan against all of them.

The MBSA scan’s have the nice, self-explanatory reports and as a bonus they are also able to check for other things than patch status.

The script:

#We need a date to append to the log files
$date = (Get-Date).toshortdatestring()

#Connect to WSUS, if needed load assembly
$wsusrv = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
If (! $?){
[
Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$wsusrv = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
}

#Get all machines in WSUS
$wsusrv.getcomputertargets() | select FullDomainName,IPAddress,RequestedTargetGroupName `
| tee -Variable wsustargets | Export-Csv -NoTypeInformation "$pwd\WSUSTargets-$date.csv"

#Part to remove certain machines which we cannot scan
#(like machines with different permissions than the bulk)
foreach ($target in $wsustargets){
if ($target.RequestedTargetGroupName -eq "SomeFolder"){
}
ElseIf ($target.RequestedTargetGroupName -like "*SomeOtherFolder*"){
}
Else {
#create array of machines to be scanned
[Array]$MBSAList += $target.IPAddress
$MBSAList | Out-File "$pwd\MBSATargets-$date.csv"
}
}

#execute MBSA against the array created. This array must have been loaded to a file
$mbsacli = "C:\Program Files\Microsoft Baseline Security Analyzer 2\mbsacli.exe"
$cmdline = "/listfile " + '"' + "$pwd\MBSATargets-$date.csv" + '"' + `
" /n OS+SQL+IIS+Password /nvc /wa /o " + '"%d% - %c% - %IP% (%t%)"' + `
" /qe /qr /rd D:\MBSA_scan_auto"
$cmdline
$process
= [System.Diagnostics.Process]::Start($mbsacli,$cmdline)
$process.WaitForExit()





Now, very off character for me, I have added some comments in the script, but it is fairly straight forward.



A list of machines is gathered from the WSUS server and stuffed in a file. The MBSAcli.exe utility is then executed using that file. It is of course possible to execute the MBSAcli.exe using the powershell array:



$mbsacli = "C:\Program Files\Microsoft Baseline Security Analyzer 2\mbsacli.exe"
foreach ($target in $MBSAList){
$cmdline = "/target $target /n OS+SQL+IIS+Password /nvc /wa /o " + `
'"%d% - %c% - %IP% (%t%)"' + " /qe /qr /rd D:\MBSA_scan_auto"
$process = [System.Diagnostics.Process]::Start($mbsacli,$cmdline)
$process.WaitForExit()
}



But the load of MBSA before each scan will lengthen the scan and will consume more resources (it’s not friendly as it is :-) )



But of course there will be machines which cannot be scanned for whatever reason. Above I excluded the once I was sure would fail (due to other permissions) , but some machines may be offline, some with special permissions may hide somewhere etc.



So in order to get an overview of which machines that do not have a report, I did the following script:



param($mbsareportstore = "SomeFolder")

#Connect to WSUS, if needed load assembly
$wsusrv = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
If (! $?){
[
Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$wsusrv = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
}

#Get all machines in WSUS
$wsusrv.getcomputertargets() | select FullDomainName,IPAddress |tee -Variable wsustargets `
| Export-Csv -NoTypeInformation "$pwd\NewWSUSTargets-$date.csv"

#Get all reports in the scanfile store
(Get-ChildItem $mbsareportstore | select name | tee -Variable reports)

#Create the status csv file
"Machine,Status,Reportname" | out-file "$PWD\FindReportStatus.txt"

#Do something...
foreach ($target in $wsustargets){

#because the targets are reurned from WSUS in FQDN format, but reports are stored
#with hostname the domain name is cut of.
$b = ($target.FullDomainName.ToString()).Split(".")
$c = $b[0]

#Using Select-string to look for the hostname ($c) in each of the filenames
$reports | % {select-String -pattern $c -inputobject $_ | tee -variable slct}

#If the hostname is not found in any filename, this is logged in the status file
#and added to a new targetfile for MBSA to use
#Write-Host lines can be removed if script is not executed interactively
if ($slct -eq $null){
Write-Host "Report Not found for " $target.FullDomainName -foregroundcolor red
"$c,ReportNotFound,N/A"
| out-file -append "$PWD\FindReportStatus.txt"
$target.FullDomainName | Out-File -Append "$PWD\MBSAScanMissing.txt"
}
Else {
Write-Host "Report found for " $target.FullDomainName -foregroundcolor green
"$c,ReportFound,$slct"
| out-file -append "$PWD\FindReportStatus.txt"
$slct = $Null
}
}



This script simply gets the list from WSUS and checks whether a report of each machine exists in the report store. The 2 scripts could be fused into one, but for my purposes, where I need to run script 1 on a regular basis and script 2 on demand, this is the best way.

1 comment: