Way back in the day, it was all VBScript and HTAs for me. I built a number of HTA tools for other people to use. As you might expect they didn't always work and troubleshooting something I couldn't see was difficult. So I came up with a solution to use an Internet Explorer window as a sort of immediate or trace window. In my HTA I added code to use this trace window. The user could then copy and paste the contents of the window and I would have a much better idea about what went wrong. I always thought this was a cool trick and figured why not do it in PowerShell?
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Using the COM object you can create an instance of Internet Explorer and then use DOM methods and properties to configure the window and write text to it. Here's the PowerShell version of what I used to do in VBScript.
#requires -version 2.0 Function Trace-Message { <# .Synopsis Display script trace messages .Description This command will launch an instance of Internet Explorer and display user defined trace messages. The trace message will automatically include a time stamp. After your script has completed, the window will remain open until you manually close it. If you use this command in the PowerShell ISE, you can use the -Terminate parameter to close it and clean up the COM object. The function has parameters to adjust the positioning, size and format of the Internet Explorer window. You can't change the position or window size once the window is first created, although you can manually reposition and resize. But it is possible to specify different values for these settings on a per message basis: Title Background Color Font FontSize The beginning of every trace window will include information about the user and computer running the script. This can be useful when troubleshooting a script that someone else is running. IMPORTANT: The trace window will only be created if the function detects a variable in the current scope, usually the script, called TraceEnabled with a value of $True. This means your script can have trace commands but they won't do anything unless $TraceEnabled is set to $True. .Notes Last Updated: October 30, 2013 Version : 0.9 .LinkFriday Fun: Create a PowerShell Trace Window#> [cmdletbinding(DefaultParameterSetName="Trace")] Param ( [Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a trace message", ParameterSetName="Trace")] [string]$Message, [Parameter(ParameterSetName="Trace")] [int]$Top=10, [Parameter(ParameterSetName="Trace")] [int]$Left=10, [Parameter(ParameterSetName="Trace")] [int]$Width=600, [Parameter(ParameterSetName="Trace")] [int]$Height=600, [Parameter(ParameterSetName="Trace")] [string]$BGColor="#ffff00", [Parameter(ParameterSetName="Trace")] [string]$Title="Trace Messages", [Parameter(ParameterSetName="Trace")] [string]$Font="Verdana", [Parameter(ParameterSetName="Trace")] [int]$FontSize=2, [Parameter(ParameterSetName="Kill")] [alias("kill")] [switch]$Terminate ) if ($Terminate) { $script:IEWindow.Quit() if ($IEWindow) { Remove-Variable -name IEWindow -Scope Script } return } #only run if $TraceEnabled is True if ($script:TraceEnabled) { #if there isn't an IE window object, create it If (-NOT $script:IEWindow) { $script:IEWindow = New-Object -ComObject "InternetExplorer.Application" $script:IEWindow.navigate("about:blank") $script:IEWindow.ToolBar = $False $script:IEWindow.AddressBar = $False $script:IEWindow.Top = $Top $script:IEWindow.Left = $Left $script:IEWindow.Width = $Width $script:IEWindow.Height = $Height $script:IEWindow.Visible = $True $script:IEWindow.menubar = $False $script:IEWindow.StatusBar = $False $script:IEWindow.Document.IHTMLDocument2_Title = $Title $script:IEWindow.document.IHTMLDocument2_bgColor= $BGColor #include some default information $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - User: $($env:USERDOMAIN)\$($env:USERName) <br>") $elevated = ([security.principal.windowsprincipal]([security.principal.windowsidentity]::Getcurrent())).IsInRole([system.security.principal.securityidentifier]"S-1-5-32-544") $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - Elevated: $Elevated <br>") $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - Computer: $env:Computername <br>") $os = Get-WmiObject win32_operatingsystem $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - OS: $($os.caption) <br>") $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - Ver.: $($os.version) <br>") $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - Service Pack: $($os.ServicePackMajorVersion).$($os.ServicePackMinorVersion) <br>") $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - Architecture: $($os.OSArchitecture) <br>") $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - ******************************<br>") } #write the message to the window $script:IEWindow.Document.IHTMLDocument2_writeln("<font face=$Font size=$FontSize> $(Get-Date) - $Message <br>") $script:IEWindow.Document.IHTMLDocument2_Title = $Title $script:IEWindow.document.IHTMLDocument2_bgColor= $BGColor } #if $TraceEnabled } #close Trace-Message function Set-Alias -Name Trace -Value Trace-Message
The function is written with the assumption that you will use it within a script. I'll have an example in a moment. The function first checks for a variable, presumably in the script scope, called TraceEnabled. If it is set to True, the function will continue. Then it checks to see if an existing trace window has already been created. If not, it creates it using some default properties for size, position, and color all of which you can change via parameters. Once the the window is created, your message string is written to the Internet Explorer window. I prepend the current date and time to the message. I also include some default user and system information when the IE window is first created. In the past this was often helpful, especially when dealing with less tech savvy users.
When you include the function in a script, everything should be cleaned up when you close the windows. The IE window will remain even after the script ends. This is handy because you can print from it, copy and paste or whatever. If you run your script from the PowerShell ISE, things don't always clean up nicely so I added a -Terminate parameter (with an alias of -Kill). When you use this in the ISE run Trace -Kill to close the window and clean up.
So how would you use this?
Well, here's a version of a script (which I don't think I've published before so you get a bonus today) that gets some basic server health information and creates an HTML report.
#requires -version 3.0 <# ServerHealth.ps1 A script to do a quick health check and create an HTML report. This version includes my trace function. **************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** #> [cmdletbinding()] Param( [Parameter(Position=0,HelpMessage="Enter the computer name for the report")] [ValidateNotNullorEmpty()] [Alias("name","cn")] [string]$Computername=$env:computername, [Parameter(Position=1,HelpMessage="Enter the path for the html report")] [ValidateNotNullorEmpty()] [string]$Path="ServerHealth.htm", [Alias("RunAs")] [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, [switch]$Trace ) #dot source the Trace function . C:\scripts\Trace.ps1 if ($trace) { $script:TraceEnabled = $True } # !!!!!! #dot source the Trace function . C:\scripts\Trace.ps1 if ($trace) { #set the variable to turn tracing on $script:TraceEnabled = $True } #!!!!!!! #the trace commands will only work if $TraceEnabled = $True Trace "Starting $($MyInvocation.MyCommand)" Write-Verbose "Starting $($MyInvocation.MyCommand)" #initialize an array for HTML fragments Trace "initializing fragments" $fragments=@() $ReportTitle = "Server Health Report: $($Computername.toUpper())" Trace $ReportTitle Trace "Defining head" #this must be left justified $head = @" <Title>$ReportTitle</Title> <style> body { background-color:#FFFFFF; font-family:Tahoma; font-size:12pt; } td, th { border:1px solid black; border-collapse:collapse; } th { color:white; background-color:black; } table, tr, td, th { padding: 2px; margin: 0px } tr:nth-child(odd) {background-color: lightgray} table { width:95%;margin-left:5px; margin-bottom:20px;} .alert {background-color: red ! important} .warn {background-color: yellow ! important} </style> <br> <H1>$ReportTitle</H1> "@ #build a hashtable of parameters for New-CimSession $cimParams=@{ ErrorAction="Stop" ErrorVariable="myErr" Computername=$Computername } Trace "Reporting on computer $Computername" if ($credential.username) { Write-Verbose "Adding a PSCredential for $($Credential.username)" Trace "Adding a PSCredential for $($Credential.username)" $cimParams.Add("Credential",$Credential) } Try { #verify if computer is running PowerShell 2 or 3 Write-Verbose "Test-WSMan $Computername" Trace "Test-WSMan $Computername" $wsman = Test-WSMan -ComputerName $Computername -ErrorAction Stop -ErrorVariable myErr Trace ($wsman | out-string) } Catch { Write-Warning "Failed to test WSMan for $Computername" Write-Warning $myErr.ErrorRecord Trace "Oops" Trace "Failed to test WSMan for $Computername" Trace "Breaking out" Break } if ([int]($wsman.ProductVersion.Substring(25)) -lt 3) { #running less than PowerShell 3 so create a DCOM option Write-Verbose "$Computername running PowerShell 2.0" Trace "$Computername running PowerShell 2.0" Trace "Creating new CimSessionOption" $cimOption = New-CimSessionOption -Protocol Dcom $cimParams.Add("SessionOption",$cimOption) } #create a CIM Session Write-Verbose "Creating a CIM Session" Trace "Creating a CIM Session" Try { $cs = New-CimSession @cimParams } Catch { Write-Warning "Failed to create CIM session for $Computername" Write-Warning $myErr.ErrorRecord Trace "Oops" Trace "Failed to create CIM session for $Computername" Trace "Breaking" Break } #get OS data and uptime Write-Verbose "Getting OS and uptime" Trace "Getting OS and uptime" $os = $cs | Get-CimInstance -ClassName Win32_OperatingSystem $fragments+="<h2>Operating System</h2>" Trace "Converting OS data to HTML fragment" $fragments+= $os | select @{Name="Computername";Expression={$_.CSName}}, @{Name="Operating System";Expression={$_.Caption}}, @{Name="Service Pack";Expression={$_.CSDVersion}},LastBootUpTime, @{Name="Uptime";Expression={(Get-Date) - $_.LastBootUpTime}} | ConvertTo-HTML -Fragment Write-Verbose "Getting memory usage" Trace "Converting memory usage to HTML fragment" $fragments+="<h2>Memory Usage</h2>" [xml]$html= $os | Select @{Name="TotalMemoryMB";Expression={[int]($_.TotalVisibleMemorySize/1mb)}}, @{Name="FreeMemoryMB";Expression={[math]::Round($_.FreePhysicalMemory/1MB,2)}}, @{Name="PercentFreeMemory";Expression={[math]::Round(($_.FreePhysicalMemory/$_.TotalVisibleMemorySize)*100,2)}}, @{Name="TotalVirtualMemoryMB";Expression={[int]($_.TotalVirtualMemorySize/1mb)}}, @{Name="FreeVirtualMemoryMB";Expression={[math]::Round($_.FreeVirtualMemory/1MB,2)}}, @{Name="PercentFreeVirtualMemory";Expression={[math]::Round(($_.FreeVirtualMemory/$_.TotalVirtualMemorySize)*100,2)}} | ConvertTo-Html -Fragment #parse html to add color attributes Trace "Parsing memory fragment" for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the percent free memory column and assign a class to the row if (($html.table.tr[$i].td[2] -as [double]) -le 10) { $class.value = "alert" $html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null } elseif (($html.table.tr[$i].td[2] -as [double]) -le 20) { $class.value = "warn" $html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null } } #add the new HTML to the fragment Trace "adding fragment" $fragments+= $html.innerXML #get disk drive status Write-Verbose "Getting drive status" Trace "Getting drive status" $drives = $cs | Get-CimInstance -ClassName Win32_Logicaldisk -filter "DriveType=3" $fragments+="<h2>Disk Usage</h2>" Trace "Converting disk usage to HTML fragment" [xml]$html= $drives | Select DeviceID, @{Name="SizeGB";Expression={[int]($_.Size/1GB)}}, @{Name="FreeGB";Expression={[math]::Round($_.Freespace/1GB,4)}}, @{Name="PercentFree";Expression={[math]::Round(($_.freespace/$_.size)*100,2)}} | ConvertTo-Html -Fragment #parse html to add color attributes Trace "Parsing memory fragment" for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the percent free column and assign a class to the row if (($html.table.tr[$i].td[3] -as [double]) -le 10) { $class.value = "alert" $html.table.tr[$i].ChildNodes[3].Attributes.Append($class) | Out-Null } elseif (($html.table.tr[$i].td[3] -as [double]) -le 20) { $class.value = "warn" $html.table.tr[$i].ChildNodes[3].Attributes.Append($class) | Out-Null } } Trace "adding disk fragment" $fragments+=$html.InnerXml Clear-Variable html #get recent errors and warning in all logs Write-Verbose "Getting recent eventlog errors and warnings" Trace "Getting recent eventlog errors and warnings" #any errors or audit failures in the last 24 hours will be displayed in red #warnings in last 24 hours will be displayed in yellow $Yesterday = (Get-Date).Date.AddDays(-1) Trace "Yesterday is $yesterday" $after = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($yesterday) Trace "Converted $yesterday to $after" #get all event logs with entries Trace "get all event logs with entries" $logs = $cs | Get-CimInstance win32_ntEventlogFile -filter "NumberOfRecords > 0" #exclude security log $fragments+="<h2>Event Logs</h2>" #process security event log for Audit Failures $fragments+="<h3>Security</h3>" Trace "Getting security event log data and converting to HTML fragment" [xml]$html = $cs | Get-CimInstance Win32_NTLogEvent -filter "Logfile = 'Security' AND Type = 'FailureAudit' AND TimeGenerated >= ""$after""" | Select -property TimeGenerated,Type,EventCode,SourceName,Message | ConvertTo-Html -Fragment Trace "Parsing event log fragment" if ($html.table) { #if a failure in the last day, display in red if (($html.table.tr[$i].td[1] -eq 'FailureAudit') -AND ([datetime]($html.table.tr[$i].td[0]) -gt $yesterday)) { $class.value = "alert" $html.table.tr[$i].Attributes.Append($class) | Out-Null } Trace "Adding event log fragment" $fragments+=$html.InnerXml } Else { #no recent audit failures Write-Verbose "No recent audit failures" $fragments+="<p style='color:green;'>No recent audit failures</p>" } Clear-Variable html Trace "Getting all other event logs" foreach ($log in ($logs | where logfilename -ne 'Security')) { Write-Verbose "Processing event log $($log.LogfileName)" $fragments+="<h3>$($log.LogfileName)</h3>" Trace "Converting event log data to HTML fragment" [xml]$html = $cs | Get-CimInstance Win32_NTLogEvent -filter "Logfile = ""$($log.logfilename)"" AND Type <> 'Information' AND TimeGenerated >= ""$after""" | Select -property TimeGenerated,Type,EventCode,SourceName,Message | ConvertTo-Html -Fragment Trace "Parsing fragment" if ($html.table) { #color errors in red for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the entry type column and assign a class to the row if within the last day if (($html.table.tr[$i].td[1] -eq 'Error') -AND ([datetime]($html.table.tr[$i].td[0]) -gt $yesterday)) { $class.value = "alert" $html.table.tr[$i].Attributes.Append($class) | Out-Null } elseif (($html.table.tr[$i].td[1] -eq 'Warning') -AND ([datetime]($html.table.tr[$i].td[0]) -gt $yesterday)) { $class.value = "warn" $html.table.tr[$i].Attributes.Append($class) | Out-Null } } #for $fragments+=$html.InnerXml } else { #no errors or warnings Write-Verbose "No recent errors or warnings for $($log.logfilename)" Trace "No recent errors or warnings for $($log.logfilename)" $fragments+="<p style='color:green;'>No recent errors or warnings</p>" } Clear-Variable html } #foreach #get services that should be running but aren't Write-Verbose "Getting services that should be running but aren't" Trace "Getting services that should be running but aren't" $services = $cs | Get-CimInstance -ClassName win32_service -filter "startmode='Auto' AND state <> 'Running'" $fragments+="<h2>Services</h2>" Trace "Converting service data to HTML fragment" $fragments+= $services | Select Name,Displayname,Description,State | ConvertTo-Html -Fragment $fragments+="<br><i>Created $(Get-Date)</i>" #create the HTML report Write-Verbose "Creating an HTML report" Trace "Creating an HTML report" Trace "Writing to $path" ConvertTo-Html -Head $head -Title $reportTitle -body $Fragments | Out-File -FilePath $path -Encoding ascii Trace (get-item $path | out-string) Write-Verbose "Saving the HTML report to $Path" Write-Verbose "Ending $($MyInvocation.MyCommand)" Trace "Ending $($MyInvocation.MyCommand)" Write-Host "Report saved to $path" -ForegroundColor Green
As you look through this you'll see a number of Trace commands. The trace script, which is dot sourced in this script, creates an alias of Trace. These commands won't do anything unless $TraceEnabled is set to $True. This version of the script includes a -Trace switch which does just that. From there the script runs and all of my trace messages are written to an IE window.
S:\ServerHealth-Trace.ps1 -Computername CHI-HVR2.GLOBOMANTICS.LOCAL -Credential globomantics\administrator -Trace -Path c:\work\hvr2-health.htm
If there is a problem, then I can see where the script is breaking down or failing. If all goes well, I end up with a report like this:
The server health script gets some information using Get-CIMInstance on memory, disk usage and event logs from that last day. The script will dynamically create a DCOM option if it detects that the remote computer is not running PowerShell 3.
You can use my Trace-Message function in conjunction with Write-Verbose. You can display the same information or different. It is up to you. You could probably accomplish the same result by using a Windows form or WPF via ShowUI. But everyone has Internet Explorer and this was pretty easy to pull together.
By the way, if you just want a copy of the server health script without the Trace commands, here you go:
#requires -version 3.0 <# ServerHealth.ps1 A script to do a quick health check and create an HTML report **************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** #> [cmdletbinding()] Param( [Parameter(Position=0,HelpMessage="Enter the computer name for the report")] [ValidateNotNullorEmpty()] [Alias("name","cn")] [string]$Computername=$env:computername, [Parameter(Position=1,HelpMessage="Enter the path for the html report")] [ValidateNotNullorEmpty()] [string]$Path="ServerHealth.htm", [Alias("RunAs")] [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty ) Write-Verbose "Starting $($MyInvocation.MyCommand)" #initialize an array for HTML fragments $fragments=@() $ReportTitle = "Server Health Report: $($Computername.toUpper())" #this must be left justified $head = @" <Title>$ReportTitle</Title> <style> body { background-color:#FFFFFF; font-family:Tahoma; font-size:12pt; } td, th { border:1px solid black; border-collapse:collapse; } th { color:white; background-color:black; } table, tr, td, th { padding: 2px; margin: 0px } tr:nth-child(odd) {background-color: lightgray} table { width:95%;margin-left:5px; margin-bottom:20px;} .alert {background-color: red ! important} .warn {background-color: yellow ! important} </style> <br> <H1>$ReportTitle</H1> "@ #build a hashtable of parameters for New-CimSession $cimParams=@{ ErrorAction="Stop" ErrorVariable="myErr" Computername=$Computername } if ($credential.username) { Write-Verbose "Adding a PSCredential for $($Credential.username)" $cimParams.Add("Credential",$Credential) } Try { #verify if computer is running PowerShell 2 or 3 Write-Verbose "Test-WSMan $Computername" $wsman = Test-WSMan -ComputerName $Computername -ErrorAction Stop -ErrorVariable myErr } Catch { Write-Warning "Failed to test WSMan for $Computername" Write-Warning $myErr.ErrorRecord Break } if ([int]($wsman.ProductVersion.Substring(25)) -lt 3) { #running less than PowerShell 3 so create a DCOM option Write-Verbose "$Computername running PowerShell 2.0" $cimOption = New-CimSessionOption -Protocol Dcom $cimParams.Add("SessionOption",$cimOption) } #create a CIM Session Write-Verbose "Creating a CIM Session" Try { $cs = New-CimSession @cimParams } Catch { Write-Warning "Failed to create CIM session for $Computername" Write-Warning $myErr.ErrorRecord Break } #get OS data and uptime Write-Verbose "Getting OS and uptime" $os = $cs | Get-CimInstance -ClassName Win32_OperatingSystem $fragments+="<h2>Operating System</h2>" $fragments+= $os | select @{Name="Computername";Expression={$_.CSName}}, @{Name="Operating System";Expression={$_.Caption}}, @{Name="Service Pack";Expression={$_.CSDVersion}},LastBootUpTime, @{Name="Uptime";Expression={(Get-Date) - $_.LastBootUpTime}} | ConvertTo-HTML -Fragment Write-Verbose "Getting memory usage" $fragments+="<h2>Memory Usage</h2>" [xml]$html= $os | Select @{Name="TotalMemoryMB";Expression={[int]($_.TotalVisibleMemorySize/1mb)}}, @{Name="FreeMemoryMB";Expression={[math]::Round($_.FreePhysicalMemory/1MB,2)}}, @{Name="PercentFreeMemory";Expression={[math]::Round(($_.FreePhysicalMemory/$_.TotalVisibleMemorySize)*100,2)}}, @{Name="TotalVirtualMemoryMB";Expression={[int]($_.TotalVirtualMemorySize/1mb)}}, @{Name="FreeVirtualMemoryMB";Expression={[math]::Round($_.FreeVirtualMemory/1MB,2)}}, @{Name="PercentFreeVirtualMemory";Expression={[math]::Round(($_.FreeVirtualMemory/$_.TotalVirtualMemorySize)*100,2)}} | ConvertTo-Html -Fragment #parse html to add color attributes for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the percent free memory column and assign a class to the row if (($html.table.tr[$i].td[2] -as [double]) -le 10) { $class.value = "alert" $html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null } elseif (($html.table.tr[$i].td[2] -as [double]) -le 20) { $class.value = "warn" $html.table.tr[$i].ChildNodes[2].Attributes.Append($class) | Out-Null } } #add the new HTML to the fragment $fragments+= $html.innerXML #get disk drive status Write-Verbose "Getting drive status" $drives = $cs | Get-CimInstance -ClassName Win32_Logicaldisk -filter "DriveType=3" $fragments+="<h2>Disk Usage</h2>" [xml]$html= $drives | Select DeviceID, @{Name="SizeGB";Expression={[int]($_.Size/1GB)}}, @{Name="FreeGB";Expression={[math]::Round($_.Freespace/1GB,4)}}, @{Name="PercentFree";Expression={[math]::Round(($_.freespace/$_.size)*100,2)}} | ConvertTo-Html -Fragment #parse html to add color attributes for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the percent free column and assign a class to the row if (($html.table.tr[$i].td[3] -as [double]) -le 10) { $class.value = "alert" $html.table.tr[$i].ChildNodes[3].Attributes.Append($class) | Out-Null } elseif (($html.table.tr[$i].td[3] -as [double]) -le 20) { $class.value = "warn" $html.table.tr[$i].ChildNodes[3].Attributes.Append($class) | Out-Null } } $fragments+=$html.InnerXml Clear-Variable html #get recent errors and warning in all logs Write-Verbose "Getting recent eventlog errors and warnings" #any errors or audit failures in the last 24 hours will be displayed in red #warnings in last 24 hours will be displayed in yellow $Yesterday = (Get-Date).Date.AddDays(-1) $after = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($yesterday) #get all event logs with entries $logs = $cs | Get-CimInstance win32_ntEventlogFile -filter "NumberOfRecords > 0" #exclude security log $fragments+="<h2>Event Logs</h2>" #process security event log for Audit Failures $fragments+="<h3>Security</h3>" [xml]$html = $cs | Get-CimInstance Win32_NTLogEvent -filter "Logfile = 'Security' AND Type = 'FailureAudit' AND TimeGenerated >= ""$after""" | Select -property TimeGenerated,Type,EventCode,SourceName,Message | ConvertTo-Html -Fragment if ($html.table) { #if a failure in the last day, display in red if (($html.table.tr[$i].td[1] -eq 'FailureAudit') -AND ([datetime]($html.table.tr[$i].td[0]) -gt $yesterday)) { $class.value = "alert" $html.table.tr[$i].Attributes.Append($class) | Out-Null } $fragments+=$html.InnerXml } Else { #no recent audit failures Write-Verbose "No recent audit failures" $fragments+="<p style='color:green;'>No recent audit failures</p>" } Clear-Variable html #process all the other logs foreach ($log in ($logs | where logfilename -ne 'Security')) { Write-Verbose "Processing event log $($log.LogfileName)" $fragments+="<h3>$($log.LogfileName)</h3>" [xml]$html = $cs | Get-CimInstance Win32_NTLogEvent -filter "Logfile = ""$($log.logfilename)"" AND Type <> 'Information' AND TimeGenerated >= ""$after""" | Select -property TimeGenerated,Type,EventCode,SourceName,Message | ConvertTo-Html -Fragment if ($html.table) { #color errors in red for ($i=1;$i -le $html.table.tr.count-1;$i++) { $class = $html.CreateAttribute("class") #check the value of the entry type column and assign a class to the row if within the last day if (($html.table.tr[$i].td[1] -eq 'Error') -AND ([datetime]($html.table.tr[$i].td[0]) -gt $yesterday)) { $class.value = "alert" $html.table.tr[$i].Attributes.Append($class) | Out-Null } elseif (($html.table.tr[$i].td[1] -eq 'Warning') -AND ([datetime]($html.table.tr[$i].td[0]) -gt $yesterday)) { $class.value = "warn" $html.table.tr[$i].Attributes.Append($class) | Out-Null } } #for $fragments+=$html.InnerXml } else { #no errors or warnings Write-Verbose "No recent errors or warnings for $($log.logfilename)" $fragments+="<p style='color:green;'>No recent errors or warnings</p>" } Clear-Variable html } #foreach #get services that should be running but aren't Write-Verbose "Getting services that should be running but aren't" $services = $cs | Get-CimInstance -ClassName win32_service -filter "startmode='Auto' AND state <> 'Running'" $fragments+="<h2>Services</h2>" $fragments+= $services | Select Name,Displayname,Description,State | ConvertTo-Html -Fragment $fragments+="<br><i>Created $(Get-Date)</i>" #create the HTML report Write-Verbose "Creating an HTML report" ConvertTo-Html -Head $head -Title $reportTitle -body $Fragments | Out-File -FilePath $path -Encoding ascii Write-Verbose "Saving the HTML report to $Path" Write-Verbose "Ending $($MyInvocation.MyCommand)" Write-Host "Report saved to $path" -ForegroundColor Green
Today you get twice the fun. What do you think about all of this? As always, I look forward to hearing what you think.
Great job Jeffery 😉 Thanks for sharing this !
It reminds me the ‘Trace’ when I was programming with Visual Studio 6 in C++ (1999) …
With DebugView from Sysinternals and this code in your script, you can make debug traces :
#need debugview : http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
[System.Diagnostics.Debug]::Write(“Hello World”)
That is kinda cool. I think you need to set up filtering but very do-able. Thanks.
Thanks Jeff – More help than code an it is very useful.