1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
<# .NOTES Name: Get-WSUSReport Author: Scott Babcock .SYNOPSIS Gets WSUS information for a specific group and genreates an HTML email report. .DESCRIPTION The script Get-WSUSReport loads the WSUS assembly and makes a connection to the WSUS server in your environment. It targets a specific group and checks for the status of updates on each server within the group. The HTML report lists a table of updates per server and a second table of servers per update. .EXAMPLE [PS] C:\>.\Get-WsusReport.ps1 <no parameters> The script does not have any parameters. #> # Create empty arrays. $UpdateStatus = @() $SummaryStatus = @() $ServersPerUpdate = @() # Load WSUS assembly. [reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | Out-Null # Connect to WSUS server and set the connection object into a variable. $WSUS = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer("wsus.get-mailbox.net", $true, 8531) # Record the last time that WSUS was syncronized with updates. $LastSync = ($wsus.GetSubscription()).LastSynchronizationTime # Create a default update scope object. $UpdateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope # Modify the update scope ApprovedStates value from "Any" to "LatesRevisionApproved". $UpdateScope.ApprovedStates = [Microsoft.UpdateServices.Administration.ApprovedStates]::LatestRevisionApproved # Create a computerscope object for use as an a requred part of a method below. $ComputerScope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope # Get the "Servers" WSUS group using a hardcoded ID value. $ComputerTargetGroups = $WSUS.GetComputerTargetGroups() ` | Where { $_.id -eq 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' } # Get all the computers objects that are members of the "Servers" group and set into variable $MemberOfGroup = $wsus.getComputerTargetGroup($ComputerTargetGroups.Id).GetComputerTargets() # Use a foreach loop to process summaries per computer for each member of the "Servers" group. ` # Then populate an array with a updates needed. Foreach ($Object in $wsus.GetSummariesPerComputerTarget($updatescope, $computerscope)) { # Use a nested foreach to process the CES Mail Servers members. foreach ($object1 in $MemberOfGroup) { # Use an if statement to match the wsus objects that contain update summaries with # the members of the CES Mail servers members. If ($object.computertargetid -match $object1.id) { # Set the fulldomain name of the CES Mail Server member in a variable. $ComputerTargetToUpdate = $wsus.GetComputerTargetByName($object1.FullDomainName) # Filter the server for updates that are marked for install with the state # being either downloaded or notinstalled. These are updates that are needed. $NeededUpdate = $ComputerTargetToUpdate.GetUpdateInstallationInfoPerUpdate() ` | where { ($_.UpdateApprovalAction -eq "install") -and ` (($_.UpdateInstallationState -eq "downloaded") -or ` ($_.UpdateInstallationState -eq "notinstalled")) } # Null out the following variables so that they don't contaminate # op_addition variables in the below nested foreach loop. $FailedUpdateReport = $null $NeededUpdateReport = $null # Use a nested foreach loop to accumulate and convert the needed updates to the KB number with URL in # an HTML format. if ($NeededUpdate -ne $null) { foreach ($Update in $NeededUpdate) { $myObject2 = New-Object -TypeName PSObject $myObject2 | add-member -type Noteproperty -Name Server -Value (($object1 | select -ExpandProperty FullDomainName) -replace ".FQDN", "") $myObject2 | add-member -type Noteproperty -Name Update -Value ('<a href' + '=' + '"' + ($wsus.GetUpdate([Guid]$update.updateid).AdditionalInformationUrls) + '"' + '>' + (($wsus.GetUpdate([Guid]$update.updateid)).title) + '<' + '/' + 'a' + '>') $UpdateStatus += $myObject2 if ($Update.UpdateInstallationState -eq "Failed") { $FailedUpdateReport += ('<a href' + '=' + '"' + ($wsus.GetUpdate([Guid]$update.updateid).AdditionalInformationUrls) ` + '"' + '>' + "(" + (($wsus.GetUpdate([Guid]$update.updateid)).KnowledgebaseArticles) + ") " + '<' + '/' + 'a' + '>') } if ($Update.UpdateInstallationState -eq "Notinstalled" -or $Update.UpdateInstallationState -eq "Downloaded") { $NeededUpdateReport += ('<a href' + '=' + '"' + ($wsus.GetUpdate([Guid]$update.updateid).AdditionalInformationUrls) ` + '"' + '>' + "(" + (($wsus.GetUpdate([Guid]$update.updateid)).KnowledgebaseArticles) + ") " + '<' + '/' + 'a' + '>') } } } # Create a custom PSObject to contain summary data about each server and updates needed. $myObject1 = New-Object -TypeName PSObject $myObject1 | add-member -type Noteproperty -Name Server -Value (($object1 | select -ExpandProperty FullDomainName) -replace ".FQDN", "") $myObject1 | add-member -type Noteproperty -Name UnkownCount -Value $object.UnknownCount $myObject1 | add-member -type Noteproperty -Name NotInstalledCount -Value $object.NotInstalledCount $myObject1 | add-member -type Noteproperty -Name NotApplicable -Value $object.NotApplicableCount $myObject1 | add-member -type Noteproperty -Name DownloadedCount -Value $object.DownloadedCount $myObject1 | add-member -type Noteproperty -Name InstalledCount -Value $object.InstalledCount $myObject1 | add-member -type Noteproperty -Name InstalledPendingRebootCount -Value $object.InstalledPendingRebootCount $myObject1 | add-member -type Noteproperty -Name FailedCount -Value $object.FailedCount $myObject1 | add-member -type Noteproperty -Name ComputerTargetId -Value $object.ComputerTargetId $myObject1 | add-member -type Noteproperty -Name NeededCount -Value ($NeededUpdate | measure).count $myObject1 | add-member -type Noteproperty -Name Failed -Value $FailedUpdateReport $myObject1 | add-member -type Noteproperty -Name Needed -Value $NeededUpdateReport $SummaryStatus += $myObject1 } } } $uniqueupdates = $UpdateStatus | sort -Unique update | select update foreach ($uniqueupdate in $uniqueupdates) { $servers = $null $myObject3 = New-Object -TypeName PSObject $myObject3 | add-member -type Noteproperty -Name Update -Value $uniqueupdate.update foreach ($object in $UpdateStatus) { if ($object.Update -eq $uniqueupdate.update) { $servers += $object.server + " " } } $myObject3 | add-member -type Noteproperty -Name Servers -Value $servers $ServersPerUpdate += $myObject3 } # Rewrite the array and eliminate servers that have 0 for needed updates. $SummaryStatus = $SummaryStatus | where { $_.neededcount -ne 0 } | sort server # List a summary of changes in a special table leveraging the "First" table class style listed above. $WSUSHead += "<table class=`"First`">`r`n" # Note the LastSync time. $WSUSHead += "<tr><td class=`"First`"><b>Last Sync:</b></td><td class=`"First`"> " + ` $LastSync + "</td></tr>`r`n" $WSUSHead += "</Body>`r`n" $WSUSHead += "</Style>`r`n" $WSUSHead += "</Head>`r`n" # Create a generic HTML Header to use throughout the script for the body of the ` # email message with table styles to control the formatting of any tables present it it. $HTMLHead = "<Html xmlns=`"http://www.w3.org/1999/xhtml`">`r`n" $HTMLHead += "<Head>`r`n" $HTMLHead += "<Style>`r`n" $HTMLHead += "TABLE{border: 1px solid black; border-collapse: collapse; font-family: Arial, Helvetica, sans-serif; font-size: 8pt;}`r`n" $HTMLHead += "TH{border: 1px solid black; background: #dddddd; padding: 5px; color: #000000;}`r`n" $HTMLHead += "TD{border: 1px solid black; padding: 5px;}`r`n" $HTMLHead += "TABLE.First{border:1px solid #dddddd; background: #f6f6f6;}`r`n" $HTMLHead += "TD.First{border:1px solid #dddddd; font-family: Arial, Helvetica, sans-serif; font-size: 8pt;}`r`n" $HTMLHead += "H3{text-align:left; font-family: Arial, Helvetica, sans-serif; font-size: 15pt;}`r`n" $HTMLHead += "HR{border: 2px dashed #848484;}`r`n" $HTMLHead += "</Style>`r`n" $HTMLHead += "</Head>`r`n" # Build a variable with HTML for sending a report. $UpdatesHTML = $HTMLHead # Continue building HTML with the updates needed $UpdatesHTML += $SummaryStatus | convertto-html -Fragment ` @{ Label = "Server"; Expression = { $_.server } }, @{ Label = "Needed Count"; Expression = { $_.NeededCount } }, @{ Label = "Not Installed"; Expression = { $_.NotInstalledCount } }, ` @{ Label = "Downloaded"; Expression = { $_.DownloadedCount } }, @{ Label = "Pending Reboot"; Expression = { $_.InstalledPendingRebootCount } }, @{ Label = "Failed Updates"; Expression = { $_.FailedCount } }, ` @{ Label = "Needed"; Expression = { $_.Needed } } $ServersHTML = $ServersPerUpdate | convertto-html -Fragment ` @{ Label = "Update"; Expression = { $_.update } }, @{ Label = "Servers"; Expression = { $_.servers } } # Add an assembly to fix up powershell HTML markup. Ensures all special characters # are converted correctly. Add-Type -AssemblyName System.Web $UpdatesHTML = [System.Web.HttpUtility]::HtmlDecode($UpdatesHTML) $ServersHTML = [System.Web.HttpUtility]::HtmlDecode($ServersHTML) # Create HTML email by adding all the various HTML sections from above. $MailMessage = " <html> <body> $WSUSHead $UpdatesHTML <br> $ServersHTML </body> </html> " # Get the date and time. $DateTime = Get-Date -Format "ddd MM/dd/yyyy h:mm tt" # Set subject line to include the $DateTime variable. $EmailSubject = "Update Status for " + $DateTime # Send an email with all the compiled data. [string[]]$EmailTo = "babcockscott@Get-Mailbox.net" Send-MailMessage -To $EmailTo ` -Subject $EmailSubject -From "donotreply@Get-Mailbox.net" ` -Body $MailMessage -BodyasHTML ` -SmtpServer "MailRelayServer" |
This script is indeed a very nice piece of art!!
Thank you very much for sharing, I have it already running in our production systems ;)
Best regards,
Hi,
Great work. would u be so kind to tell me how to save the HTML fiel locally ?!?
Regards.
Red.
Hi,
I am trying to copy the code but it looks like there is a lot of html added. I am doing something wrong??
Great script, i was hoping someone could help me how i could add a filter to only include updates under the “Windows” classification. i.e Server OS only, no office, SQL etc etc.?
wow .. what a script !!!!!!! excellent work!!!
i did add some code to get a local copy of that report and send it as attachment (starting at line 173):
$MailMessage | Out-File c:\temp\wsusreport.html
# Get the date and time.
$DateTime = Get-Date -Format “ddd MM/dd/yyyy h:mm tt”
# Set subject line to include the $DateTime variable.
$EmailSubject = “your subject ” + $DateTime
# Send an email with all the compiled data.
[string[]]$EmailTo = “yourmail@anymail.com”
Send-MailMessage -To $EmailTo
-Subject $EmailSubject -From "yourmail@anymail.com"
-Body $MailMessage -BodyasHTML
-attachment "c:\temp\wsusreport.html"
-SmtpServer “yourmailgateway”
Fantastic script! This is EXACTLY what I was hoping to do, but much nicer than how I was going to do it. What I’d like to do with it is save the htmls of all the computer groups (which the script already does) to a folder within inetpub\wwwroot and would like to know if there’s possibly a way to have it generate an index.html with links to the html files it created.
I ma getting below error while running the script.
Cannot find an overload for “getComputerTargetGroup” and the argument count: “1”.
At Z:\WSUS\WSUS-Report.ps1:41 char:1
+ $MemberOfGroup = $wsus.getComputerTargetGroup($ComputerTargetGroups.Id).GetCompu …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
Make sure you have changed the placeholder text “xxxxxxxxxxxxxxx” that I’ve inserted on line 39.
Exception calling “GetUpdateServer” with “3” argument(s): “The underlying connection was closed: An unexpected error occurred on a
send.”
At line:25 char:1
+ $WSUS = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer(“d …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebException
Cannot find an overload for “getComputerTargetGroup” and the argument count: “1”.
At line:41 char:1
+ $MemberOfGroup = $wsus.getComputerTargetGroup($ComputerTargetGroups.Id).GetCompu …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
Send-MailMessage : The remote name could not be resolved: ‘MailRelayServer’
At line:181 char:1
+ Send-MailMessage -To $EmailTo `
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpException
+ FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage
it works getUpdateServer(“name_wsus”, $false, port)
At line:9 char:8
+ for the status of updates on each server within the group. The H …
+ ~
Missing opening ‘(‘ after keyword ‘for’.
At line:12 char:10
+ [PS] C:\>.\Get-WsusReport.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~
Unexpected token ‘C:\>.\Get-WsusReport.ps1’ in expression or statement.
At line:12 char:35
+ [PS] C:\>.\Get-WsusReport.ps1
+ ~
The ‘<' operator is reserved for future use.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingOpenParenthesisAfterKeyword
can someone explain what im doing wrong as im getting the above errors ?? totally new to powershell
I found I was getting all the KB article links showing as one big underline, although the individual links worked if you hovered your mouse above the KB number. I added a   to separate them in the $Update.UpdateInstallationState -eq “Notinstalled” and $Update.UpdateInstallationState -eq “Failed” sections of code
I added an “$_.UpdateInstallationState -eq “failed”” as another “or” at line 59. Wasn’t getting any failed links.