Below is an example of some of the data that you can retrieve from the Get-ADComputer cmdlet about a computer/server. I’m outputting this information to a HTML document. This script is also slightly interactive as it uses Write-Host. This is for visual feedback in an interactive environment. Note that Write-Host isn’t able to output to another script or be re-used. Inventor of PowerShell, Jeffrey Snover, has a great article here about using Write-Host.
The script is below. Copy it and save it as a .PS1 file. After the script, I break down what each section of the script does.
<#
Created by : Tim Haintz
Date Created: 24/05/2017
Description : Produce an HTML report of all servers pinged but not responding.
URLs used :
https://technet.microsoft.com/en-us/library/ff730936.aspx
https://powershell.org/2012/10/24/free-ebook-creating-html-reports-in-powershell/
https://leanpub.com/creatinghtmlreportsinpowershell
https://www.w3schools.com/css/
#>
# CSS Styles for HTML output
$head = "
<style>
BODY{background-color:white;}
TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:LightSteelBlue}
TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:LightCoral}
</style>
"
# Date format correct for saving in the output location. A new file will be created each new day/date
$date = (Get-Date -Format yyyyMMdd)
$outputlocation = "$env:TEMP\$date`_test.html"
<#
Used if you would like to put individual machines in rather than using OUs
$computernames = 'dc1','ca1'
$servers = foreach ($computername in $computernames)
{
Get-ADComputer $computername -Properties *
}
#>
$ous = 'CN=Computers,DC=timhaintz,DC=com','OU=Domain Controllers,DC=timhaintz,DC=com'
Write-Host "OUs being used to search are: $($ous)"
#$servers being populated from the OUs with all of their AD attributes.
#$servers is then filtered using where-object to only objects that contain .dnshostname entries as these are 'real' machines
#$servers is then sorted alphabetically based on CN/hostname.
$servers = $ous | ForEach-Object { Get-ADComputer -Filter * -Properties * -SearchBase $_ }
$servers = $servers | Where-Object{$_.dnshostname} | Sort-Object -Property 'CN'
Write-Host "Retreived servers from OUs and sorted machines based on server name"
#$frag is a fragment used for ConvertTo-Html. It contains the interesting information.
$frag = foreach ($server in $servers)
{
Write-Host $server.cn
#If Test-Connection is false (that's what ! in the if statement means) then pass into the if statement.
if(!(Test-Connection -ComputerName $server.dnshostname -Count 1 -Quiet))
{
$server | Select-Object -Property @{Label='Computer Name'; Expression ={$_.cn}},IPv4Address,whenCreated,whenChanged
}
}
#Convert captured servers into HTML as per formatting and information required.
$frag | ConvertTo-Html -Title 'Failed PING of Servers' `
-PreContent "<h1>Below are the $($frag.count) servers that failed a PING test. $($servers.count) servers were pinged in this process.</h1>" `
-Head $head |
Out-File $outputlocation
Write-Host "File written to: $outputlocation"
Invoke-Item $outputlocation
I’m using CSS to style my HTML page. The CSS is being stored as the $head variable. This is used later on in ConvertTo-Html with the -Head parameter.
I also like to store the date in a specific format. The date is then used to generate the file name. Storing the date as yyyyMMdd (20180531) for example allows the files to be sorted nicely in date order. I also use the $env:TEMP variable as whoever is running the script has permission to this location. As below, the file will be called 20180531_test and will be located in $env:TEMP. In my test environment, $env:TEMP is: C:\Users\AZUREA~1\AppData\Local\Temp
# CSS Styles for HTML output
$head = "
<style>
BODY{background-color:white;}
TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:LightSteelBlue}
TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color:LightCoral}
</style>
"
# Date format correct for saving in the output location. A new file will be created each new day/date
$date = (Get-Date -Format yyyyMMdd)
$outputlocation = "$env:TEMP\$date`_test.html"
Get-ADComputer can’t search multiple OUs with the -SearchBase parameter. For this reason, we have to store the OUs as separate strings and then run them through the ForEach-Object cmdlet.
$ous = 'CN=Computers,DC=timhaintz,DC=com','OU=Domain Controllers,DC=timhaintz,DC=com'
is where I have the computers stored in Active Directory.
$servers = $ous | ForEach-Object { Get-ADComputer -Filter * -Properties * -SearchBase $_ }
searches through each of the OUs for the machines I’m looking for and stores them in the $servers variable.
Get-ADComputer cmdlet, I’m using -Filter * and -Properties *
to retrieve all machines and also all information about those machines.
$servers = $servers | Where-Object{$_.dnshostname} | Sort-Object -Property 'CN'
Checks that the computer object contains a dnshostname value. I also use Sort-Object to sort the list of servers from their CN value or server name.
Write-Host is being used to present feedback to the user running the script.
$ous = 'CN=Computers,DC=timhaintz,DC=com','OU=Domain Controllers,DC=timhaintz,DC=com'
Write-Host "OUs being used to search are: $($ous)"
$servers = $ous | ForEach-Object { Get-ADComputer -Filter * -properties * -SearchBase $_ }
$servers = $servers | Where-Object{$_.dnshostname} | Sort-Object -Property 'CN'
Write-Host "Retreived servers from OUs and sorted machines based on server name"
Using the ForEach statement I loop through each server that was collected in $servers.
Again, I’m using Write-Host to provide feedback to the person running the script.
Using an If statement and Test-Connection, logic can be used to decide what information to look for. Below, I’m using if(!(Test-Connection -Quiet)). The ! reverses the logic to be NOT. -Quiet returns a boolean value. If Test-Connection fails, with the ! mark, the if statement evaluates TRUE and enters the if statement. Remove the ! and it will capture the information of servers where Test-Connection is successful.
To present the HTML report with nice labels, I’m using Select-Object and a calculated property to rename the property from cn to Computer Name. @{Label=’Computer Name’; Expression ={$_.cn}} is the calculated property. I’m also selecting IPv4Address, whenCreated and whenChanged. Anything that is gathered from Get-ADComputer -Properties * can be used in Select-Object -Property. For example, badPwdCount or LastLogonDate could also be included if that information was of interest.
The results of $server | Select-Object … are stored in the $frag variable to be used later with ConvertTo-Html. |
#$frag is a fragment used for ConvertTo-Html. It contains the interesting information.
$frag = foreach ($server in $servers)
{
Write-Host $server.cn
#If Test-Connection is false (that's what ! in the if statement means) then pass into the if statement.
if(!(Test-Connection -ComputerName $server.dnshostname -Count 1 -Quiet))
{
$server | Select-Object -Property @{Label='Computer Name'; Expression ={$_.cn}},IPv4Address,whenCreated,whenChanged
}
}
$frag is piped into ConvertTo-Html. Using the -Title, -PreContent, and -Head parameters of ConvertTo-Html, the CSS in $head is used to present the $frag information and display it in a table.
In my test environment, the contents of $frag is as below:
PS C:\Users\azureadmin\Documents> $frag
Computer Name IPv4Address whenCreated whenChanged
------------- ----------- ----------- -----------
Srv1 10.0.0.8 5/31/2018 8:06:30 PM 5/31/2018 8:07:20 PM
Srv2 10.0.0.7 5/30/2018 8:39:30 PM 5/30/2018 8:40:21 PM
This information is then piped to Out-File and saved to $outputlocation.
I then call Invoke-Item $outputlocation to open the HTML document automatically.
#Convert captured servers into HTML as per formatting and information required.
$frag | ConvertTo-Html -Title 'Failed PING of Servers' `
-PreContent "<h1>Below are the $($frag.count) servers that failed a PING test. $($servers.count) servers were pinged in this process.</h1>" `
-Head $head |
Out-File $outputlocation
Write-Host "File written to: $outputlocation"
Invoke-Item $outputlocation
A screenshot of the report from my test environment is shown below:
This is a nice visual way of displaying if any of your servers or computers are no longer ‘pingable’ and may need attention.
I was asked by @ottafish how to send the HTML report via email. Using a very helpful post from A Guide to Microsoft Products I setup a SendGrid SMTP relay in Azure. The script using Send-MailMessage and SendGrid is below. I have added a screen shot of the email I received after each option of code.
For $cred, I used:
$cred = Get-Credential
and used the credentials for SendGrid. You could also use Export-Clixml and Import-Clixml if you wanted to automate entering credentials. I will be doing a blog post in the coming weeks around Export-Clixml and Import-Clixml.
$body = Get-Content $ouputlocation -Raw
Send-MailMessage -Subject 'Test HTML message' -Body $body -BodyAsHtml -Credential $cred -From <from@address.com> -To <to@address.com> -SmtpServer smtp.sendgrid.net -UseSsl -Port 587
Send-MailMessage -Subject 'Test HTML message' -Attachments $outputlocation -Credential $cred -From <from@address.com> -To <to@address.com> -SmtpServer smtp.sendgrid.net -UseSsl -Port 587
# If you don't want to store a file at all, modify the last part of the script to as below
$body = $frag | ConvertTo-Html -Title 'Failed PING of Servers' `
-PreContent "<h1>Below are the $($frag.count) servers that failed a PING test. $($servers.count) servers were pinged in this process.</h1>" `
-Head $head
Send-MailMessage -Subject 'Test HTML message' -Body $($body) -BodyAsHtml -Credential $cred -From <from@address.com> -To <to@address.com> -SmtpServer smtp.sendgrid.net -UseSsl -Port 587
As shown above, these are a few ways you can send HTML reports via email.
Hope you’re having a great day and this is of use.
Thanks, Tim.