Category Archives: Windows Server

Morning Report Revised

Last month I posted a PowerShell script I called The Morning Report. I received some very nice feedback. One comment was about making it easier to use the script in a pipelined expression. For example, get a list of computers from a text file and create a single HTML report. That sounds reasonable to me so I decided to revisit the script and add a few tweaks.

The latest version can still be used as I originally described. But now the computername parameter can be piped in to the script.


PS C:\> $report=get-content computers.txt | c:\scripts\morningreport.ps1

The report will still write a collection of custom report objects to the pipeline. But now I can do this:


PS C:\> get-content computers.txt | c:\scripts\morningreport.ps1 -text | out-file c:\work\morning-report.txt

This will create a single text file for all computers in the list. If you prefer a separate file per computer, use a ForEach loop.


PS C:\> get-content computers.txt | foreach {
>> $comp=$_
>> c:\scripts\morningreport.ps1 -Comp $comp -text |
>> out-file "c:\work\$comp-morning-report.txt"
>> }
>>

Similarly, I can now create a single HTML file.


PS C:\> get-content computers.txt | c:\scripts\morningreport.ps1 -html -hours 36 | out-file c:\work\morning-report.htm

I had to revise the HTML related code a bit to accommodate multiple computers in the same file. I modified the navigation links to the computername and inserted a list of computers at the beginning of the file. Clicking on a computername will take you to the corresponding summary. I probably wouldn’t try this with hundreds of computers, but for most small to mid-size groups I think this will work just fine.

The last change I made, which was as much for me as anyone, was to add a -Quick parameter. Querying event logs is time consuming so if you are in a hurry you can skip the event log query by using -Quick. You’ll still get an event log section in the output, but it will be empty.

Download the revised version of MorningReport.ps1 and let me know what you think.

PowerShell in a Nutshell

This past weekend I did an online presentation for a friend of mine who teaches for ITT in Omaha, Nebraska. He wanted me to do a brief talk about what PowerShell is and show how to use it, especially for managing Active Directory. I probably went much longer than I needed but everyone seemed to get a lot out of it. The session was recorded via WebEx. I then transcoded the recording so I could get it up on YouTube. That’s why the screen layout is a little funky and the audio is far from perfect. Still, I hope you find it useful. The presentation has a some slides but is primarily demo, including using the Microsoft Active Directory cmdlets.

You can also download my presentation and a zip filewith my demo scripts.

If you are ready to learn more check out some of the books and training videos in the side bar. Or bring me in to run a private PowerShell class for your organization. Good Luck and Enjoy!

The PowerShell Morning Report

ZazuI love how easy it is to manage computers with Windows PowerShell. It is a great reporting tool, but often I find people getting locked into one approach. I’m a big believer in flexibility and re-use and using objects in the pipeline wherever I can. So I put together a PowerShell script that I can run every morning on my computer and get a quick summary about what is happening, or perhaps not happening. My script, MorningReport.ps1, relies on WMI to gather a variety of system information. By default it connects to the local host, but I’ve provided a computername parameter. I’m assuming current credentials are good enough for any remote system, but you can always add support for alternate credentials, assuming I don’t in some future version.

By default the script writes a custom object to the pipeline that contains all of the other WMI information like disk utilization, service status, and event logs. But I wanted flexibility and ease of use, so the script also supports parameters of -Text and -HTML. The former creates nicely formatted text suitable for printing or saving to a file. The latter creates HTML code from the original objects. I rely heavily on creating HTML fragments with ConvertTo-HTML and then assembling everything at the end. The script writes the HTML code to the pipeline so if you want to save results to a file, simply pipe to Out-File. I did this because there may be times when you want the “raw” HTML code. You might want to save the HTML and create an HTML mail message with Send-MailMessage. Or maybe further tweak the HTML before saving it to a file. Again, I didn’t want to lock myself in. Here’s the main part of the script.


[cmdletbinding(DefaultParameterSetName="object")]

Param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[ValidateNotNullOrEmpty()]
[string]$Computername=$env:computername,
[ValidateNotNullOrEmpty()]
[alias("title")]
[string]$ReportTitle="System Report",
[ValidateScript({$_ -ge 1})]
[int]$Hours=24,
[Parameter(ParameterSetName="HTML")]
[switch]$HTML,
[Parameter(ParameterSetName="TEXT")]
[switch]$Text
)

#script internal version number used in output
[string]$reportVersion="1.0.8"

<#
define some HTML style
here's a source for HTML color codes

http://www.immigration-usa.com/html_colors.html

the code must be left justified
#>
$head = @"

$ReportTitle
"@

If ($computername -eq $env:computername) {
#local computer so no ping test is necessary
$OK=$True
}
elseIf (($computername -ne $env:computername) -AND (Test-Connection -ComputerName $computername -quiet -Count 2)) {
#not local computer and it can be pinged so proceed
$OK=$True
}

If ($OK) {

Try {
$os=Get-WmiObject Win32_operatingSystem -ComputerName $computername -ErrorAction Stop
#set a variable to indicate WMI can be reached
$wmi=$True
}
Catch {
Write-Warning "WMI failed to connect to $($computername.ToUpper())"
}

if ($wmi) {
Write-Host "Preparing morning report for $($os.CSname)" -ForegroundColor Cyan

#OS Summary
Write-Host "...Operating System" -ForegroundColor Cyan
$osdata=$os | Select @{Name="Computername";Expression={$_.CSName}},
@{Name="OS";Expression={$_.Caption}},
@{Name="ServicePack";Expression={$_.CSDVersion}},
free*memory,totalv*,NumberOfProcesses,
@{Name="LastBoot";Expression={$_.ConvertToDateTime($_.LastBootupTime)}},
@{Name="Uptime";Expression={(Get-Date) - ($_.ConvertToDateTime($_.LastBootupTime))}}

#Computer system
Write-Host "...Computer System" -ForegroundColor Cyan
$cs=Get-WmiObject -Class Win32_Computersystem -ComputerName $computername
$csdata=$cs | Select Status,Manufacturer,Model,SystemType,Number*

#services
Write-Host "...Services" -ForegroundColor Cyan
#get services via WMI and group into a hash table
$wmiservices=Get-WmiObject -class Win32_Service -ComputerName $computername
$services=$wmiservices | Group State -AsHashTable

#get services set to auto start that are not running
$failedAutoStart=$wmiservices | Where { ($_.startmode -eq "Auto") -AND ($_.state -ne "Running")}

#Disk Utilization
Write-Host "...Logical Disks" -ForegroundColor Cyan
$disks=Get-WmiObject -Class Win32_logicaldisk -Filter "Drivetype=3" -ComputerName $computername
$diskData=$disks | Select DeviceID,
@{Name="SizeGB";Expression={$_.size/1GB -as [int]}},
@{Name="FreeGB";Expression={"{0:N2}" -f ($_.Freespace/1GB)}},
@{Name="PercentFree";Expression={"{0:P2}" -f ($_.Freespace/$_.Size)}}

#NetworkAdapters
Write-Host "...Network Adapters" -ForegroundColor Cyan
#get NICS that have a MAC address only
$nics=Get-WmiObject -Class Win32_NetworkAdapter -filter "MACAddress Like '%'" -ComputerName $Computername
$nicdata=$nics | Foreach {
$tempHash=@{Name=$_.Name;DeviceID=$_.DeviceID;AdapterType=$_.AdapterType;MACAddress=$_.MACAddress}
#get related configuation information
$config=$_.GetRelated() | where {$_.__CLASS -eq "Win32_NetworkadapterConfiguration"}
#add to temporary hash
$tempHash.Add("IPAddress",$config.IPAddress)
$tempHash.Add("IPSubnet",$config.IPSubnet)
$tempHash.Add("DefaultGateway",$config.DefaultIPGateway)
$tempHash.Add("DHCP",$config.DHCPEnabled)
#convert lease information if found
if ($config.DHCPEnabled -AND $config.DHCPLeaseObtained) {
$tempHash.Add("DHCPLeaseExpires",($config.ConvertToDateTime($config.DHCPLeaseExpires)))
$tempHash.Add("DHCPLeaseObtained",($config.ConvertToDateTime($config.DHCPLeaseObtained)))
$tempHash.Add("DHCPServer",$config.DHCPServer)
}

New-Object -TypeName PSObject -Property $tempHash

}

#Event log errors and warnings in the last $Hours hours
$last=(Get-Date).AddHours(-$Hours)
#System Log
Write-Host "...System Event Log Error/Warning since $last" -ForegroundColor Cyan
$syslog=Get-EventLog -LogName System -ComputerName $computername -EntryType Error,Warning -After $last
$syslogdata=$syslog | Select TimeGenerated,EventID,Source,Message

#Application Log
Write-Host "...Application Event Log Error/Warning since $last" -ForegroundColor Cyan
$applog=Get-EventLog -LogName Application -ComputerName $computername -EntryType Error,Warning -After $last
$applogdata=$applog | Select TimeGenerated,EventID,Source,Message

} #if wmi is ok

#write results depending on parameter set
$footer="Report v{3} run {0} by {1}\{2}" -f (Get-Date),$env:USERDOMAIN,$env:USERNAME,$reportVersion

if ($HTML) {
#prepare HTML code
$fragments=@()
#insert a graphic header with one of the two following lines
#$fragments+="

"
$fragments+="Zazu

The Morning Report

"

#insert navigation bookmarks
$nav=@"
Services
Failed Auto Start
Disks
Network
System Log
Application Log
"@
$fragments+=$nav
$fragments+="
"

#add a link to the document top
$nav+="`nTop"

$fragments+="

System Summary

"
$fragments+=$osdata | ConvertTo-Html -as List -Fragment
$fragments+=$csdata | ConvertTo-Html -as List -Fragment
$fragments+=ConvertTo-Html -Fragment -PreContent "

Services

"
$services.keys | foreach {
$fragments+= ConvertTo-Html -Fragment -PreContent "

$_

"
$fragments+=$services.$_ | Select Name,Displayname,StartMode| ConvertTo-HTML -Fragment
#insert navigation link after each section
$fragments+=$nav
}

$fragments+=$failedAutoStart | Select Name,Displayname,StartMode,State |
ConvertTo-Html -Fragment -PreContent "

Failed Auto Start

"
$fragments+=$nav

$fragments+=$diskdata | ConvertTo-HTML -Fragment -PreContent "

Disk Utilization

"
$fragments+=$nav

#convert nested object array properties to strings
$fragments+=$nicdata | Select Name,DeviceID,DHCP*,AdapterType,MACAddress,
@{Name="IPAddress";Expression={$_.IPAddress | Out-String}},
@{Name="IPSubnet";Expression={$_.IPSubnet | Out-String}},
@{Name="IPGateway";Expression={$_.DefaultGateway | Out-String}} |
ConvertTo-HTML -Fragment -PreContent "

Network Adapters

"
$fragments+=$nav

$fragments+=$syslogData | ConvertTo-HTML -Fragment -PreContent "

System Event Log Summary

"
$fragments+=$nav

$fragments+=$applogData | ConvertTo-HTML -Fragment -PreContent "

Application Event Log Summary

"
$fragments+=$nav

Write $fragments | clip
ConvertTo-Html -Head $head -Title $ReportTitle -PreContent ($fragments | out-String) -PostContent "
$footer"
}
elseif ($TEXT) {
#prepare formatted text
$ReportTitle
"-"*($ReportTitle.Length)
"System Summary"
$osdata | Out-String
$csdata | format-List | Out-String
Write "Services"
$services.keys | foreach {
$services.$_ | Select Name,Displayname,StartMode,State
} | Format-List | Out-String
Write "Failed Autostart Services"
$failedAutoStart | Select Name,Displayname,StartMode,State
Write "Disk Utilization"
$diskdata | Format-table -AutoSize | Out-String
Write "Network Adapters"
$nicdata | Format-List | Out-String
Write "System Event Log Summary"
$syslogdata | Format-List | Out-String
Write "Application Event Log Summary"
$applogdata | Format-List | Out-String
Write $Footer
}
else {
#Write data to the pipeline as part of a custom object

New-Object -TypeName PSObject -Property @{
OperatingSystem=$osdata
ComputerSystem=$csdata
Services=$services.keys | foreach {$services.$_ | Select Name,Displayname,StartMode,State}
FailedAutoStart=$failedAutoStart | Select Name,Displayname,StartMode,State
Disks=$diskData
Network=$nicData
SystemLog=$syslogdata
ApplicationLog=$applogdata
ReportVersion=$reportVersion
RunDate=Get-Date
RunBy="$env:USERDOMAIN\$env:USERNAME"
}
}

} #if OK

else {
#can't ping computer so fail
Write-Warning "Failed to ping $computername"
}

As you can see, it is a lengthy script, but I’ve tried to include a fair amount of internal comments and documentation, so I won’t repeat it here, but I will touch on a few key points.

First, I’m embedding a style sheet directly in any HTML output so any files I create can stand alone. You could certainly modify the relevant sections and use the -CSSUri parameter with ConvertTo-HTML. Next, I use a combination of Test-Connection and Try/Catch to handle computers that are offline or I can’t access. This speeds up the script and makes it nicer for the script user. Assuming all is good, I create a number of variables that hold the WMI information I am interested in.


...
#OS Summary
Write-Host "...Operating System" -ForegroundColor Cyan
$osdata=$os | Select @{Name="Computername";Expression={$_.CSName}},
@{Name="OS";Expression={$_.Caption}},
@{Name="ServicePack";Expression={$_.CSDVersion}},
free*memory,totalv*,NumberOfProcesses,
@{Name="LastBoot";Expression={$_.ConvertToDateTime($_.LastBootupTime)}},
@{Name="Uptime";Expression={(Get-Date) - ($_.ConvertToDateTime($_.LastBootupTime))}}

#Computer system
Write-Host "...Computer System" -ForegroundColor Cyan
$cs=Get-WmiObject -Class Win32_Computersystem -ComputerName $computername
$csdata=$cs | Select Status,Manufacturer,Model,SystemType,Number*
...

After everything is collected, then I can write output using an If/ElseIf statement depending on what parameters were passed. If no parameters were specified then a custom object is assembled and written to the pipeline.


else {
#Write data to the pipeline as part of a custom object

New-Object -TypeName PSObject -Property @{
OperatingSystem=$osdata
ComputerSystem=$csdata
Services=$services.keys | foreach {$services.$_ | Select Name,Displayname,StartMode,State}
FailedAutoStart=$failedAutoStart | Select Name,Displayname,StartMode,State
Disks=$diskData
Network=$nicData
SystemLog=$syslogdata
ApplicationLog=$applogdata
ReportVersion=$reportVersion
RunDate=Get-Date
RunBy="$env:USERDOMAIN\$env:USERNAME"
}

But perhaps you’d like to see this in action. Here’s a sample HTML SampleReport. In this report, there are no recent errors or warnings in the Application event log.

Or if you have a few minutes, here’s a short clip of the script in action.

I hope you find this a useful jumping off point for your own script, although I think I’ve already worked out information you are most likely interested in. Download MorningReport.ps1 and try it out for yourself.

Update: Read about a revised version that accepts pipelined input.