PowerShell Scripting Help - Generate a System Uptime Report that will send an daily html email blast to my team : < 30 days critcal systems as RED, 21 to 29 days as Yellow, and 0 to 20 days GREEN
Hi PowerShell Gurus,
Happy New Year Guys!
Once again, I need your help.
The company I work for has tasked me with generating a daily report, html preferred sent via email, of the Uptime of Windows Servers in our environment.
1. Query only active servers in our domains (Get-AdComputer -Filter {OperatingSystem -like "*windows*server*"} but I am having issues with the syntax of only active\enabled systems)
2. Generate an "Uptime Report\Date of Report" in an excel-like html format with systems that have been up longer than 30 days as red, systems that will need a reboot soon (21 to 29 days) yellow, and systems that are still within the 0 to 20 days window, as green
3. Email a list of users, daily, with this report so they can remediate these systems during our scheduled maintenance window
I found the system uptime code on the internet:
<#.SYNOPSISOutputs the last bootup time and uptime for one or more computers..DESCRIPTIONOutputs the last bootup time and uptime for one or more computers..PARAMETER ComputerNameOne or more computer names. The default is the current computer. Wildcards are not supported..PARAMETER CredentialSpecifies credentials that have permission to connect to the remote computer. This parameter is ignored for the current computer..OUTPUTSPSObjects containing the computer name, the last bootup time, and the uptime.#>[CmdletBinding()]param( [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] $ComputerName, [System.Management.Automation.PSCredential] $Credential)begin { function Out-Object { param( [System.Collections.Hashtable[]] $hashData ) $order = @() $result = @{} $hashData | ForEach-Object { $order += ($_.Keys -as [Array])[0] $result += $_ } New-Object PSObject -Property $result | Select-Object $order } function Format-TimeSpan { process { "{0:00} d {1:00} h {2:00} m {3:00} s" -f $_.Days,$_.Hours,$_.Minutes,$_.Seconds } } function Get-Uptime { param( $computerName, $credential ) # In case pipeline input contains ComputerName property if ( $computerName.ComputerName ) { $computerName = $computerName.ComputerName } if ( (-not $computerName) -or ($computerName -eq ".") ) { $computerName = [Net.Dns]::GetHostName() } $params = @{ "Class" = "Win32_OperatingSystem" "ComputerName" = $computerName "Namespace" = "root\CIMV2" } if ( $credential ) { # Ignore -Credential for current computer if ( $computerName -ne [Net.Dns]::GetHostName() ) { $params.Add("Credential", $credential) } } try { $wmiOS = Get-WmiObject @params -ErrorAction Stop } catch { Write-Error -Exception (New-Object $_.Exception.GetType().FullName ` ("Cannot connect to the computer '$computerName' due to the following error: '$($_.Exception.Message)'", $_.Exception)) return } $lastBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($wmiOS.LastBootUpTime) Out-Object ` @{"ComputerName" = $computerName}, @{"LastBootTime" = $lastBootTime}, @{"Uptime" = (Get-Date) - $lastBootTime | Format-TimeSpan} }}process { if ( $ComputerName ) { foreach ( $computerNameItem in $ComputerName ) { Get-Uptime $computerNameItem $Credential } } else { Get-Uptime "." }}
These are my results (Beginning Stages) - still receiving RPC errors:
PS G:\Status Report Script> Get-ADComputer -Filter {OperatingSystem -like "*windows*server*"}|Where {$_.Enabled -eq $true}|Select-Object -ExpandProperty Name | Sort-Object | .\Get-Uptime.ps1ComputerName LastBootTime Uptime ------------ ------------ ------ ********* 12/17/2018 5:35:38 AM 15 d 14 h 35 m 24 s Get-Uptime : Cannot connect to the computer '***********' due to the following error: 'The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)'At G:\Status Report Script\Get-Uptime.ps1:91 char:7+ Get-Uptime $computerNameItem $Credential+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], COMException + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Get-UptimeGet-Uptime : Cannot connect to the computer '**********' due to the following error: 'The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)'At G:\Status Report Script\Get-Uptime.ps1:91 char:7+ Get-Uptime $computerNameItem $Credential+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], COMException + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Get-Uptime'**********' 12/26/2018 12:38:04 PM 06 d 07 h 32 m 59 s Get-Uptime : Cannot connect to the computer '**********' due to the following error: 'The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)'At G:\Status Report Script\Get-Uptime.ps1:91 char:7+ Get-Uptime $computerNameItem $Credential+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], COMException + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Get-UptimeGet-Uptime : Cannot connect to the computer '**********' due to the following error: 'The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)'At G:\Status Report Script\Get-Uptime.ps1:91 char:7+ Get-Uptime $computerNameItem $Credential+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], COMException + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Get-Uptime '**********' 12/20/2018 6:08:48 AM 12 d 14 h 02 m 36 s '**********' 12/20/2018 6:07:51 AM 12 d 14 h 03 m 33 s Get-Uptime : Cannot connect to the computer '**********' due to the following error: 'The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)'At G:\Status Report Script\Get-Uptime.ps1:91 char:7+ Get-Uptime $computerNameItem $Credential+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Write-Error], COMException + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Get-Uptime
So, I am asking the experts what do I need to do to put this all together? I have an SMTP server that is accessible, sent messages to my work account today, but I am not sure how to set this script up as I need it and the business is wanting it. :(
"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.
You're probably better off using a Test-Connection (which is a ping) instead of Enabled/Disabled. That will help avoid the RPC not available message (not completely, but it will help a lot).
I did some playing with that WMI call, and the date time isn't easily parsed as a datetime object, From what I can see you have to break it out to convert it to datetime object. Since it's a very straightforward string, it'd be easy to parse it out manually to get a date time object. Are all your machines in a single timezone? (that matters if they are going to be contacted from a different timezone).
I reworked it, but left some of it to do... (primarily building the CSS rules for the HTML body at line 41).. I'm sure there will be suggestions to optimize this, but it should get you going.. The main thing is that the ComputerName parameter will take multiple entries, and it will then process them. So, you could use your Get-ADComputer line and pipe it directly to this function, or gather the names into a single variable and use that as the input to -ComputerName, etc..
Coralon: Thanks a ton for writing that up for me! I will test it and let you know how it is working out for me!
Eric: I think your assumptions are correct. There are some firewall rules that need to be implemented to get WRM and RPC calls enabled. Thanks for that article. I will get with our network guy to make the necessary changes.
I will report back soon!
Mark
The IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more to help build customer satisfaction and retention.
Just don't forget... the HTML sections need some work.. (I ran out of time to work it out, since I'm not much of an HTML guy).. :-)
Coralon
Mark RoddySystem AdministratorAuthor Commented:
Hi Guys,
Unfortunately I am not a html guru myself. I ran it again today, tweaking your script a little bit, but it was throwing errors rendering the html pages.
I will post some more tomorrow. Getting ready to go to bed.
Yeah it would.. there's a lot more that needs to be done for this. I'm actually using my own answer here as a template for a very similar thing here at work.. once I get this worked out, I'll try to remember to post the sanitized version here..
I'm glad to help here if you'll post the errors.
Coralon
Mark RoddySystem AdministratorAuthor Commented:
Hi Coralon,
Thanks for the assist! Still receiving RPC errors when trying to enumerate the found systems BUT I am still having issues with writing to the html array. Here is my current code:
RPC errors are still popping up when systems report back they are active. I ran the Test-WSMan on a few of them and they reported back clean. No issues BUT I do not know if it is erroring out on systems that were erroring out on before due to firewall issues
Windows PowerShell
Copyright (C) 2014 Microsoft Corporation. All rights reserved.
And of course, it can't find anything in the array so it errors out on the sending the email..
Send-MailMessage : Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.At line:107 char:92+ ... SMTPPort -Body ($HTML -join $CRLF) -BodyAsHtml+ ~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidData: (:) [Send-MailMessage], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.SendMailMessage
I did a lot of reworking on my own script today.. and I reworked most of the HTML pieces..
What I ended up doing was creating here strings for all the html major pieces, and added functions for each of them. Here's a pseudo code type of layout of the changes I made:
Add-Type -AssemblyName System.Web$HTMLBody = @'<html> <head> <style> </style> </head> <body> <div> <table>'@$HTMLRow = @' <tr> <th class="_CLASS_">_HEADER1_</th> <td class="_CLASS_">_data1</td> </tr>'@$HTMLClose = @' </table> </div> </body></html>$OutputList = New-Object -Type System.Collections.ArrayListfunction New-HTML { $OutputList.Add($HTMLBody)}function New-HTMLRow{ param( [string]$HeaderString, [string]$DataString ) $HeaderString = [system.web.httputility]::HTMLEncode($HeaderString) $DataString = [system.web.httputility]::HTMLEncode($HeaderString) $TempData = $HTMLRow.Replace('_HEADER1_', $HeaderString) $TempData = $TempData.Replace('_DATA1_', $DataString) $HTMLBody.Add($TempData)}#And more functions for each of the HTML snippets, and then inside the function, just calling the function with the correct data.
When the script is done, then you just output the $OutputString ($OutputString.ToString()) or if you want a file, you output it to a file.. like $OutputString -join "`r`n" | out-file c:\Temp.html
Coralon
Mark RoddySystem AdministratorAuthor Commented:
Hi Coralon,
I will let you know my results. Thank you so much for your help.
Mark
Mark RoddySystem AdministratorAuthor Commented:
Hi Coralon,
What parts do I need to replace in the original script you gave me?
Can you post a full example, if it needs to be sanitized that is ok by me!
Thank you,
Mark
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for freeEdge Out The Competitionfor your dream job with proven skills and certifications.Get started todayStand Outas the employee with proven skills.Start learning today for freeMove Your Career Forwardwith certification training in the latest technologies.Start your trial today
I did some playing with that WMI call, and the date time isn't easily parsed as a datetime object, From what I can see you have to break it out to convert it to datetime object. Since it's a very straightforward string, it'd be easy to parse it out manually to get a date time object. Are all your machines in a single timezone? (that matters if they are going to be contacted from a different timezone).
I reworked it, but left some of it to do... (primarily building the CSS rules for the HTML body at line 41).. I'm sure there will be suggestions to optimize this, but it should get you going.. The main thing is that the ComputerName parameter will take multiple entries, and it will then process them. So, you could use your Get-ADComputer line and pipe it directly to this function, or gather the names into a single variable and use that as the input to -ComputerName, etc..
Open in new window
More error checking could be added, or the html could be dumped to a file and sent as an attachment, etc.. but it should be a good start..