Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Friday Fun: Create a PowerShell Trace Window

Posted on November 1, 2013November 20, 2015

magnifying-glassWay 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?

Manage and Report Active Directory, Exchange and Microsoft 365 with
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

.Link
Friday 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

trace

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:

serverhealth

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.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

4 thoughts on “Friday Fun: Create a PowerShell Trace Window”

  1. akismet-45ae182c501417d0a71ad3bd0676028c says:
    November 2, 2013 at 6:17 am

    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”)

    1. Jeffery Hicks says:
      November 2, 2013 at 8:37 am

      That is kinda cool. I think you need to set up filtering but very do-able. Thanks.

  2. jvierra says:
    November 3, 2013 at 2:26 pm

    Thanks Jeff – More help than code an it is very useful.

  3. Pingback: More PowerShell Trace Window Fun

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d