Please provide a rating!  If you don't feel this is worth a 4 or 5, please let me know how to improve it. 

SYNOPSIS

The purpose of this script is to provide an easy to read summarization for a large number of Jetstress result files.  This script will be most useful for deployments where Jetstress will be run on 10 or more Exchange servers.

DESCRIPTION

 This script will recursively search the current directory or a provided path which contains a large number of Jetstress XML files.  For each XML file, the script will parse the file and determine if the results passed or failed.  If the result is a failure, the script will continue to parse the XML data for key details regarding the failure (failure summary, specific instances which failed, instance database configuration).  Once the XML data has been parsed, the script will put all the details into a Summary and Detailed output variables as HTML code.  The script will create the HTML content and replace #SummaryOutput# and #DetailedOutput# with the collected data.  The end result is an HTML report for all of the XML files which were found.

 

PowerShell
Edit|Remove
<# 
[COPYRIGHT] 
© 2011-2016 Microsoft Corporation. All rights reserved.  
 
[DISCLAIMER] 
This script is not supported under any Microsoft standard support 
program or service. The script is provided AS IS without warranty of 
any kind. Microsoft disclaims all implied warranties including, without 
limitation, any implied warranties of merchantability or of fitness for a 
particular purpose. The entire risk arising out of the use or performance of 
the sample script and documentation remains with you. In no event shall 
Microsoft, its authors, or anyone else involved in the creation, production, 
or delivery of the script be liable for any damages whatsoever (including, 
without limitation, damages for loss of business profits, business 
interruption, loss of business information, or other pecuniary loss) arising 
out of the use of or inability to use the script or documentation, 
even if Microsoft has been advised of the possibility of such damages. 
 
[AUTHOR] 
Jason Parker, Consultant - Microsoft 
 
[CONTRIBUTORS] 
Dennis Swenson, Sr. Consultant - Microsoft 
 
[SCRIPT] 
Create-JetstressReport.ps1 
 
[VERSION] 
2.0 
 
[VERSION HISTORY / UPDATES] 
1.0  
Jason Parker - Original Release 
 
1.1 
Jason Parker - Incorporated the CSS and HTML support files into the script.  No support files needed. 
 
2.0 
Jason Parker - ADDED the ability to copy JetStress XML from a list of servers 
Jason Parker - ADDED additional logic to the XML gathering process to accomodate both local and file copy processes 
Jason Parker - UPDATED the HTML report so that I/O Errors are captured in addition to other errors 
#> 
 
<# 
.SYNOPSIS 
The purpose of this script is to provide an easy to read summarization for a large number of Jetstress result files. 
This script will be most useful for deployments where Jetstress will be run on 10 or more Exchange servers. 
 
.DESCRIPTION 
This script will recursively search the current directory or a provided path which contains a large number of Jetstress XML 
files.  For each XML file, the script will parse the file and determine if the results passed or failed.  If the result is a 
failure, the script will continue to parse the XML data for key details regarding the failure (failure summary, specific 
instances which failed, instance database configuration).  Once the XML data has been parsed, the script will put all the 
details into a Summary and Detailed output variables as HTML code.  The script will create the HTML content and replace 
#SummaryOutput# and #DetailedOutput# with the collected data.  The end result is an HTML report for all of the XML files 
which were found. 
 
.PARAMETER CollectionType 
[MANDATORY] This parameter only accepts 3 types of input based on which XML files are to be parsed.  This parameter will accept "Performance", 
"Stress", or "ALL".  Selecting ALL will collect both Performance and Stress XML files. 
 
.PARAMETER Servers 
This parameter accepts the server names from where the script will attempt to collect the JetStress XML files. The parameter will 
also accept a variable which contains an array of server names. 
 
.PARAMETER XMLPath 
Specifies the path to the Jetstress XML data files.  If no path is provided, the script will recursively search the directory 
from which the script is launched. 
 
.PARAMETER CreateCSV 
When used, the script will create a CSV output file with limited details regarding the scanned XML data files.  By default, the 
script will only create an HTML output. 
 
.PARAMETER CopyXMLFromServers 
Used only in conjunction with -Servers.  The script will create distinct folders for each server in the -Servers list.  Use this 
parameter to copy the XML files from the server even if the script detects a folder for the server.  Generally, a server folder 
will not exist unless the XML files have been previously copied.  This option will ensure the latest set of XML files are copied. 
 
.PARAMETER SearchRecursive 
Used only in conjunction with -XMLPath.  Specifies if the script will search the XMLPath recursively or only search the provided 
path. 
 
.NOTES 
 
.EXAMPLE 
Create-JetstressReport.ps1 -CollectionType ALL 
 
Running the script with the minimum required parameters will recursively search the current directory for Jetstress Performance and Stress XML files and will 
produce a single HTML file. 
 
.EXAMPLE 
Create-JetstressReport.ps1 -CollectionType Performance -XMLPath "C:\JetStress\" -SearchRecursive 
 
In this example, the script will recursively search the XMLPath for JetStress Performance XML files and produce a single HTML report. 
 
.EXAMPLE 
Create-JetStressReport.ps1 -CollectionType ALL -Servers Server1,Server2,Server3,Server4 
 
In this example, the script will validate the JetStress path on each server and create a local directory for that servername in the 
scripts location.  It will copy all XML files from the server, then parse them and create a single HTML report. 
 
.EXAMPLE 
Create-JetStressReport.ps1 -CollectionType ALL -CreateCSV 
 
In this example, the script will recursively search the current directory for Jetstress Performance and Stress XML files,produce a 
single HTML file and export the results to CSV. 
 
#> 
 
[CmdletBinding(PositionalBinding=$false)] 
 
Param ( 
    [Parameter (Mandatory=$true, HelpMessage="Specify which type of JetStress data to collect")] 
    [ValidateSet("Performance","Stress","ALL")] 
    [System.String]$CollectionType = "ALL", 
    [Parameter (Mandatory=$false,HelpMessage="Enter the ComputerNames where the JetStress XML files can be copied from")] 
    [System.Array]$Servers = $null, 
    [Parameter (Mandatory=$False, HelpMessage="Specify the path to the Jetstress XML data")] 
    [System.String]$XMLPath, 
    [Parameter (Mandatory=$False)] 
    [Switch]$CreateCSV, 
    [Parameter (Mandatory=$False)] 
    [Switch]$CopyXMLFromServers, 
    [Parameter (Mandatory=$false)] 
    [Switch]$SearchRecursive 
) 
 
Clear-Host 
# Creates a localized stopwatch to track script execution time 
$StopWatch = New-Object System.Diagnostics.Stopwatch 
$StopWatch.Start() 
$ScriptPath = Convert-Path .\ 
 
$myTitle = @" 
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 
\\\\\\\\\\ 
\\\\\\\\\\  Title:       Exchange JetStress XML Reporting Script 
\\\\\\\\\\  Purpose:     Collects / Uses JetStress XML files to create a unified HTML report 
\\\\\\\\\\  Parameters:  -XMLPath, -Servers, -CollectionType (Performance, Stress or ALL), -CreateCSV 
\\\\\\\\\\               -CopyXMLFromServers, -SearchRecursive 
\\\\\\\\\\  Script:      Create-JetStressReport.ps1 
\\\\\\\\\\ 
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ 
"@ 
 
Write-Host $myTitle 
 
# Specifies the HTML and CSV output file names 
$Date = (Get-Date).datetime 
$OutputHTML = (".\JetStress_Html_Results_{0}.html"-f (Get-Date -Format MMddyy_HHmmss).ToString() 
$OutputCSV = (".\JetStress_CSV_Results_{0}.csv"-f (Get-Date -Format MMddyy_HHmmss).ToString() 
 
# Checks for both -XMLPath and -Servers, only 1 parameter is allowed 
If ($XMLPath -and $Servers) { 
       Write-Warning "Using -XMLPath and -Servers together is not supported!  Please select one or the other." 
       Return 
} 
If (-not([System.String]::IsNullOrEmpty($XMLPath))) { 
    Write-Host ("`n`r Script Mode:  Local XML Parsing`n`r"-BackgroundColor Cyan -ForegroundColor Black 
    Write-Verbose (">>  Gathering XML files from: {0}" -$XMLPath) 
    If ($CollectionType -eq "Performance") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        If ($SearchRecursive) {$AllXML = Get-ChildItem -Filter Perf*.xml -Path $XMLPath -Recurse} 
        Else {$AllXML = Get-ChildItem -Filter Perf*.xml -Path $XMLPath} 
    } 
    If ($CollectionType -eq "Stress") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        If ($SearchRecursive) {$AllXML = Get-ChildItem -Filter Stress*.xml -Path $XMLPath -Recurse} 
        Else {$AllXML = Get-ChildItem -Filter Stress*.xml -Path $XMLPath} 
    } 
    If ($CollectionType -eq "ALL") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        If ($SearchRecursive) {$AllXML = Get-ChildItem -Filter *.xml -Path $XMLPath -Include "Perf*","Stress*" -Recurse} 
        Else {$AllXML = Get-ChildItem -Filter *.xml -Path $XMLPath -Include "Perf*","Stress*"} 
    } 
} 
Else { 
    Write-Host ("`n`r Script Mode:  Local XML Parsing`n`r"-BackgroundColor Cyan -ForegroundColor Black 
    Write-Verbose (">>  Gathering XML files from: {0}" -$ScriptPath) 
    If ($CollectionType -eq "Performance") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        $AllXML = Get-ChildItem -Filter Perf*.xml -Path $ScriptPath -Recurse 
    } 
    If ($CollectionType -eq "Stress") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        $AllXML = Get-ChildItem -Filter Stress*.xml -Path $ScriptPath -Recurse 
    } 
    If ($CollectionType -eq "ALL") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        $AllXML = Get-ChildItem -Filter *.xml -Path $ScriptPath -Include "Perf*","Stress*" -Recurse 
    } 
} 
If ($Servers) { 
    Write-Host ("`n`r Script Mode:  Server XML File Copy`n`r"-BackgroundColor Cyan -ForegroundColor Black 
    Write-Verbose (">>  Checking for Data Directory") 
    If (-not (Test-Path -Path .\Data)) { 
        Write-Verbose (">>  Data Directory NOT FOUND, creating Data Directory in the current location") 
        New-Item -Path .\ -Name Data -ItemType Directory -Force | Out-Null 
    } 
        
    Write-Verbose (">>  Found {0} Servers in the `$Servers parameter" -$Servers.Count) 
    $i = 1 
    ForEach ($Server in $Servers) { 
        Write-Output (">>  Processing: $Server ($i of {0})" -$Servers.Count) -Verbose 
        If (Test-Path -Path "\\$server\C$\Program Files\Exchange Jetstress") { 
            If (-not (Test-Path -Path ".\Data\$Server\*" -Include *.xml)) { 
                Write-Verbose (">>  Unable to find Server specific path locally, creating Server specific folder") 
                New-Item -Path .\Data -Name $Server -ItemType Directory -Force | Out-Null 
 
                Write-Verbose (">>  Collecting XML Files") 
                If ($CollectionType -eq "Performance") { 
                    Write-Verbose (">>  Collection Type: $CollectionType") 
                    Copy-Item -Path "\\$server\C$\Program Files\Exchange Jetstress\perf*.xml" -Destination .\Data\$Server -Force 
                } 
                If ($CollectionType -eq "Stress") { 
                    Write-Verbose (">>  Collection Type: $CollectionType") 
                    Copy-Item -Path "\\$server\C$\Program Files\Exchange Jetstress\stress*.xml" -Destination .\Data\$Server -Force 
                } 
                If ($CollectionType -eq "ALL") { 
                    Write-Verbose (">>  Collection Type: $CollectionType") 
                    Copy-Item -Path "\\$server\C$\Program Files\Exchange Jetstress\*.xml" -Include "perf*","stress*" -Destination .\Data\$Server -Force 
                } 
            } 
            Else { 
                Write-Verbose (">>  Server Data folder exists") 
                $ServerStressXML = Get-ChildItem -Filter stress*.xml -Path .\Data\$Server | Sort LastWriteTime -Descending | select -First 1 
                $ServerPerfXML = Get-ChildItem -Filter perf*.xml -Path .\Data\$Server | Sort LastWriteTime -Descending | select -First 1 
                 
                if ($CopyXMLFromServers) { 
                    Write-Verbose (">>  [`$CopyXMLFromServers ENABLED] Collecting XML Files") 
                    If ($CollectionType -eq "Performance") { 
                        Write-Verbose (">>  Collection Type: $CollectionType") 
                        Copy-Item -Path "\\$server\C$\Program Files\Exchange Jetstress\perf*.xml" -Destination .\Data\$Server -Force 
                    } 
                    If ($CollectionType -eq "Stress") { 
                        Write-Verbose (">>  Collection Type: $CollectionType") 
                        Copy-Item -Path "\\$server\C$\Program Files\Exchange Jetstress\stress*.xml" -Destination .\Data\$Server -Force 
                    } 
                    If ($CollectionType -eq "ALL") { 
                        Write-Verbose (">>  Collection Type: $CollectionType") 
                        Copy-Item -Path "\\$server\C$\Program Files\Exchange Jetstress\*.xml" -Include "perf*","stress*" -Destination .\Data\$Server -Force 
                    } 
                } 
                Else { 
                    Write-Verbose (">>  Using existing XML files") 
                    If ($CollectionType -eq "Performance") { 
                        Write-Verbose (">>  Collection Type: $CollectionType") 
                        $ServerPerfXML = Get-ChildItem -Filter perf*.xml -Path .\Data\$Server | Sort LastWriteTime -Descending | select -First 1 
                        Write-Verbose (">>  Latest Performance XML:  {0}" -$ServerPerfXML) 
                    } 
                    If ($CollectionType -eq "Stress") { 
                        Write-Verbose (">>  Collection Type: $CollectionType") 
                        $ServerStressXML = Get-ChildItem -Filter stress*.xml -Path .\Data\$Server | Sort LastWriteTime -Descending | select -First 1 
                        Write-Verbose (">>  Latest Stress XML:  {0}" -$ServerStressXML) 
                    } 
                    If ($CollectionType -eq "ALL") { 
                        Write-Verbose (">>  Collection Type: $CollectionType") 
                        $ServerPerfXML = Get-ChildItem -Filter perf*.xml -Path .\Data\$Server | Sort LastWriteTime -Descending | select -First 1 
                        Write-Verbose (">>  Latest Performance XML:  {0}" -$ServerPerfXML) 
                        $ServerStressXML = Get-ChildItem -Filter stress*.xml -Path .\Data\$Server | Sort LastWriteTime -Descending | select -First 1 
                        Write-Verbose (">>  Latest Stress XML:  {0}" -$ServerStressXML) 
                    } 
                } 
            } 
        } 
        Else {Write-Warning ("Could not find the Exchange JetStress path on $Server")} 
         
        $i++ 
    } 
    Write-Verbose (">>  Gathering XML files for processing") 
    If ($CollectionType -eq "Performance") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        $AllXML = Get-ChildItem -Filter Perf*.xml -Path .\Data -Recurse 
    } 
    If ($CollectionType -eq "Stress") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        $AllXML = Get-ChildItem -Filter Stress*.xml -Path .\Data -Recurse 
    } 
    If ($CollectionType -eq "ALL") { 
        Write-Verbose (">>  Collection Type: $CollectionType") 
        $AllXML = Get-ChildItem -Filter *.xml -Path .\Data -Recurse 
    } 
} 
 
$TotalXML = $AllXML.Count 
$XMLCounter = 1 
 
$DetailedOutput = @() 
$SummaryOutput = @() 
$csvCollection = @() 
 
#region Begin XML parsing 
Write-Host (" Parsing XML files..."-NoNewline 
ForEach ($XML in $AllXML) { 
     
    $xmlFullName = $xml.FullName 
    $xmlDirectory = $xml.DirectoryName 
    $xmlFileName = $xml.Name 
     
    Write-Progress -Activity "Parsing JetStress XML files: $XMLCounter of $TotalXML" -PercentComplete (($XMLCounter/ $TotalXML)  * 100) 
     
    [xml]$objXML = Get-Content $xmlFullName 
    $csvProperties = @('Server','Start','End','Duration','Result','Threads','Databases','IOPS','Failure','Filename','SourceFilepath') 
    $strServerName = $objXML.output.'test-summary'.element[1].value 
    $dtStart = [DateTime]$objXML.output.'test-summary'.element[3].value 
    $dtEnd = [DateTime]$objXML.output.'test-summary'.element[4].value 
    $dtDuration = ($dtEnd - $dtStart) 
    $dtDurationDetail = [string]$dtDuration.Days + " Days " + [string]$dtDuration.Hours + " Hrs " + [string]$dtDuration.Minutes + " Min" 
    $strStatus = $objXML.output.'test-summary'.element[0].value 
    $strThreads = [string]$objXML.output.'jetstress-system'.element[0].value 
    $strCopies = [string]$objXML.output.'jetstress-system'.element[9].value 
    $strDatabases = [string]$objXML.output.'database-sizing'.element[5].value 
    $strIOPS = $objXML.output.'disk-subsystem'.iops 
    $objFailures = ($objXML.output.'test-issues'.element | % {$_.value}) 
 
    if ($objFailures.count -gt "1") { 
        $objFailures | % { 
            $csvObject = @{ 
                Server = "** " + $strServerName 
                Start = $dtStart 
                End = $dtEnd 
                Duration = $dtDurationDetail 
                Result = $strStatus 
                Threads = $strThreads 
                Databases = $strDatabases 
                DatabaseCopies = $strCopies 
                IOPS = $strIOPS 
                Failure = $_ 
                Filename = $xmlFileName 
                SourceFilepath = $xmlFullName 
            } 
            $csvCollection +New-Object psobject -Property $csvObject | select $csvProperties 
        } 
    } 
    elseif ($objFailures.count -le "1") { 
        $csvObject = @{ 
            Server = $strServerName 
            Start = $dtStart 
            End = $dtEnd 
            Duration = $dtDurationDetail 
            Result = $strStatus 
            Threads = $strThreads 
            Databases = $strDatabases 
            DatabaseCopies = $strCopies 
            IOPS = $strIOPS 
            Failure = $objFailures 
            Filename = $xmlFileName 
            SourceFilepath = $xmlFullName 
        } 
        $csvCollection +New-Object psobject -Property $csvObject | select $csvProperties 
    } 
    #region Passed JetStress XML 
    if ($strStatus -eq 'Pass') { 
        #region HTML Summary Output - PASSED Results 
        $s_htmlPassed = @() 
        $s_htmlPassed = @" 
            <tr> 
                    <td class="srvrSummaryInfo1"><a href="#$xmlFullName">$strServerName</a></td> 
                    <td class="srvrSummaryPass"><strong><a href="#$xmlFullName"><font color="#FFFFFF">$strStatus</font></a></strong></td> 
                    <td class="srvrSummaryInfo2">$dtStart</td> 
                    <td class="srvrSummaryInfo2">$dtDurationDetail</td> 
                    <td class="srvrSummaryInfo2">$strIOPS</td> 
            </tr> 
 
"@ 
        $SummaryOutput +$s_htmlPassed 
        #endregion 
        #region HTML Detailed Output - PASSED Results 
        $d_htmlPassed = @() 
        $d_htmlPassed = @" 
            <p><hr align="left"/></p> 
            <table> 
                   <thead> 
                          <tr> 
                                 <td class="srvrDetailHeading1" colspan="3"><a name="$xmlFullName">$strServerName</a></td> 
                          </tr> 
                   </thead> 
                   <tbody> 
                    <tr> 
                        <th class="rowHeading">Filename:</th> 
                                 <td class="srvrDetailInfo1" colspan="2">$xmlFileName</td> 
                    </tr> 
                          <tr> 
                                 <th class="rowHeading">Test Start:</th> 
                                 <td class="srvrDetailInfo1" colspan="2">$dtStart</td> 
                          </tr> 
                          <tr> 
                                 <th class="rowHeading">Test End:</th> 
                                <td class="srvrDetailInfo1" colspan="2">$dtEnd</td> 
                          </tr> 
                          <tr> 
                                 <th class="rowHeading">Duration:</th> 
                                 <td class="srvrDetailInfo1" colspan="2">$dtDurationDetail</td> 
                          </tr> 
                          <tr> 
                                 <th class="rowHeading">Result:</th> 
                                 <td class="srvrDetailPass" colspan="2"><strong>$strStatus</strong></td> 
                          </tr> 
                          <tr> 
                                 <th class="rowHeading">Threads:</th> 
                                 <td class="srvrDetailInfo1" colspan="2">$strThreads</td> 
                          </tr> 
                          <tr> 
                                 <th class="rowHeading">Databases:</th> 
                                 <td class="srvrDetailInfo1" colspan="2">$strDatabases</td> 
                          </tr> 
                          <tr> 
                                 <th class="rowHeading">Database Copies:</th> 
                                 <td class="srvrDetailInfo1" colspan="2">$strCopies</td> 
                          </tr> 
                          <tr> 
                                 <th class="rowHeading">Achieved IOPS:</th> 
                                 <td class="srvrDetailInfo1" colspan="2">$strIOPS</td> 
                          </tr> 
                   </tbody> 
            </table> 
            <br/> 
            <span style="font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;font-size:x-small;color:black;text-align:left;padding-left:10px">Source Filepath: $xmlFullName</span> 
            <br/><br/> 
            <span style="font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;font-size:medium;color:black;text-align:left;padding-left:10px"><a href="#top">Back to top</a></span>  
 
"@ 
        $DetailedOutput +$d_htmlPassed 
        #endregion 
    } 
    #endregion 
    #region Failed JetStress XML 
    if ($strStatus -eq "fail") { 
         
        #region HTML Summary Output - FAILED Results 
        $s_htmlFailed = @" 
               <tr> 
                      <td class="srvrSummaryInfo1"><a href="#$xmlFullName">$strServerName</a></td> 
                      <td class="srvrSummaryFail"><strong><a href="#$xmlFullName"><font color="#FFFFFF">$strStatus</font></a></strong></td> 
                      <td class="srvrSummaryInfo2">$dtStart</td> 
                      <td class="srvrSummaryInfo2">$dtDurationDetail</td> 
                      <td class="srvrSummaryInfo2">$strIOPS</td> 
               </tr> 
 
"@ 
        $SummaryOutput +$s_htmlFailed 
        #endregion 
        #region HTML Detailed Output - FAILED Results 
        $d_htmlFailed = @" 
<p><hr align="left"/></p> 
<table> 
    <thead> 
        <tr><td class="srvrDetailHeading1" colspan="3"><a name="$xmlFullName">$strServerName</a></td></tr> 
    </thead> 
    <tbody> 
        <tr> 
            <th class="rowHeading">Filename:</th> 
            <td class="srvrDetailInfo1" colspan="2">$xmlFileName</td> 
        </tr> 
        <tr> 
            <th class="rowHeading">Test Start:</th> 
            <td class="srvrDetailInfo1" colspan="2">$dtStart</td> 
        </tr> 
        <tr> 
            <th class="rowHeading">Test End:</th> 
            <td class="srvrDetailInfo1" colspan="2">$dtEnd</td> 
        </tr> 
        <tr> 
            <th class="rowHeading">Duration:</th> 
            <td class="srvrDetailInfo1" colspan="2">$dtDurationDetail</td> 
        </tr> 
        <tr> 
            <th class="rowHeading">Result:</th> 
            <td class="srvrDetailFail" colspan="2"><strong>$strStatus</strong></td> 
        </tr> 
        <tr> 
            <th class="rowHeading">Threads:</th> 
            <td class="srvrDetailInfo1" colspan="2">$strThreads</td> 
        </tr> 
        <tr> 
            <th class="rowHeading">Databases:</th> 
            <td class="srvrDetailInfo1" colspan="2">$strDatabases</td> 
        </tr> 
        <tr> 
                <th class="rowHeading">Database Copies:</th> 
                <td class="srvrDetailInfo1" colspan="2">$strCopies</td> 
        </tr> 
        <tr> 
            <th class="rowHeading">Achieved IOPS:</th> 
            <td class="srvrDetailInfo1" colspan="2">$strIOPS</td> 
        </tr> 
 
"@ 
        #endregion 
        #region Failures 
        $fsHTML = @" 
        <tr><th class="srvrDetailHeading2" colspan="3">Failure Summary</th></tr> 
 
"@ 
        $d_htmlFailed +$fsHTML 
 
        if ($objFailures.count -gt "1") { 
            foreach ($objFailure in $objFailures) { 
                $htmlRow = @" 
        <tr><td class="srvrDetailInfo1" colspan="3">$objFailure</td></tr> 
 
"@ 
                $d_htmlFailed +$htmlRow 
            } 
        } 
        else { 
             
            $htmlRow = @" 
        <tr><td class="srvrDetailInfo1" colspan="3">$objFailures</td></tr> 
 
"@ 
            $d_htmlFailed +$htmlRow 
        } 
        #endregion 
        #region Disk-Subsystem Failures 
        $dsHTML = @" 
        <tr><th class="srvrDetailHeading2" colspan="3">Transactional I/O Failures</th></tr> 
 
"@ 
        $d_htmlFailed +$dsHTML 
 
        $dsCounters = @() 
        $strInstances = @() 
        $dsInstances = $objXML.output.'disk-subsystem'.object.instance 
 
        foreach ($dsInstance in $dsInstances) { 
            $dsProperties = @('Instance','Counter','Value') 
            $dsInstanceName = $dsInstance.name 
                     $dsCounters = $dsInstance.counter 
 
            foreach ($dsCounter in $dsCounters) { 
                if ($dsCounter.class -eq 'failure') { 
                    $dsCounterName = $dsCounter.name 
                    $dsCounterValue = $dsCounter.average 
                    $dsHTMLRow = @" 
        <tr> 
            <th class="rowHeading">$dsInstanceName</th> 
            <td class="srvrDetailInfo3">$dsCounterName</td> 
            <td class="srvrDetailInfo2">$dsCounterValue</td> 
        </tr> 
 
"@ 
                    $d_htmlFailed +$dsHTMLRow 
                    $strInstances +$dsInstanceName 
                } 
            } 
        } 
        #endregion 
        #region Database Configuration 
        $dcHTML = @" 
        <tr><th class="srvrDetailHeading2" colspan="3">Database Configuration</th></tr> 
 
"@ 
        $d_htmlFailed +$dcHTML 
 
        $dcInstances = $objXML.output.'database-configuration'.element 
 
        foreach ($strInstance in $strInstances) { 
            foreach ($dcInstance in $dcInstances) { 
                if ($dcInstance.Name -eq $strInstance) { 
                    $map = (($dcInstance.Value).Replace("<br/>"," ")) 
                    $map = (($map.Replace(" Database:",",Database")).Split(",")) 
                    $strLogs = $map[0] 
                    $strDBs = $map[1] 
 
                    $dcHTMLRow = @" 
        <tr> 
            <th class="rowHeading" rowspan="2">$strInstance</th> 
            <td class="srvrDetailInfo2" colspan="2">$strDBs</td> 
        </tr> 
        <tr> 
            <td class="srvrDetailInfo2" colspan="2">$strLogs</td> 
        </tr> 
 
"@ 
                    $d_htmlFailed +$dcHTMLRow 
                } 
            } 
        } 
        #endregion 
        #region Other Error Data 
        If ($objXML.output.'error-data') { 
            $edHTML = @" 
        <tr><th class="srvrDetailHeading2" colspan="3">Error Data</th></tr> 
 
"@ 
            $d_htmlFailed +$edHTML 
            Foreach ($edVolume in ($objXML.output.'error-data'.volume)) { 
                Write-Debug "Error Data Volume: $($edVolume.name)" 
                $edHTMLVolHeading = @" 
        <tr><th class="rowHeading" rowspan="5">$($edVolume.Name)</th></tr> 
 
"@ 
                $d_htmlFailed +$edHTMLVolHeading 
                foreach ($ErrorType in ($edVolume.'error-type')) { 
                    Write-Debug "Error Type: $($ErrorType.Name)" 
                    $ErrName = $ErrorType.Name 
                    $ErrText = $ErrorType.'#text' 
                    $ErrTypeHTML = @" 
        <tr> 
            <td class="srvrDetailInfo3">$ErrName</td> 
            <td class="srvrDetailInfo2">$ErrText</td> 
        </tr> 
 
"@ 
                    $d_htmlFailed +$ErrTypeHTML 
                } 
            } 
        } 
        #endregion 
 
        $htmlFooter = @" 
    </tbody> 
</table> 
<br/> 
<span style="font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;font-size:x-small;color:black;text-align:left;padding-left:10px">Source Filepath: $xmlFullName</span> 
<br/><br/> 
<span style="font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;font-size:medium;color:black;text-align:left;padding-left:10px"><a href="#top">Back to top</a></span>  
 
"@ 
        $d_htmlFailed +$htmlFooter 
        $DetailedOutput +$d_htmlFailed 
    } 
    #endregion 
    #sleep -Milliseconds 100 
    $XMLCounter++ 
} 
sleep -Milliseconds 250 
Write-Host ("Done!"-ForegroundColor Green 
 
Write-Progress -Activity "Parsing XML Completed" -Completed 
#endregion 
 
#region Build Jetstress Report Matrix 
Write-Host (" Creating HTML Template file..."-NoNewline 
$jsResults = @" 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<style> 
    .rowHeading { 
        width: 25%; 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: large; 
        text-align: right; 
        padding-right: 10px; 
        background-color: #0078D7; 
        color: #FFFFFF; 
    } 
    .srvrDetailHeading1 { 
        text-align: center; 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: x-large; 
        color: #FFFFFF; 
        background-color: #505050; 
    } 
    .srvrDetailHeading2 { 
        text-align: center; 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: large; 
        color: #FFFFFF; 
        background-color: #737373; 
    } 
    .srvrDetailInfo1 { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: large; 
        padding-left: 10px; 
    } 
    .srvrDetailInfo2 { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: medium; 
        padding-left: 10px; 
    } 
    .srvrDetailInfo3 { 
        width: 35%; 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: medium; 
        padding-left: 10px; 
    } 
    .srvrDetailPass { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: large; 
        padding-left: 10px; 
        color: #FFFFFF; 
        background-color: #107C10; 
    } 
    .srvrDetailFail { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: large; 
        padding-left: 10px; 
        color: #FFFFFF; 
        background-color: #E81123; 
    } 
    .srvrSummaryHeading1 { 
        text-align: left; 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: large; 
        color: #FFFFFF; 
        background-color: #525252; 
    } 
   .srvrSummaryHeading2 { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: large; 
        color: #FFFFFF; 
        background-color: #525252; 
    } 
    .srvrSummaryInfo1 { 
        width: 15%; 
        text-align: left; 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: medium; 
        color: #000000; 
        background-color: #FFFFFF; 
    } 
    .srvrSummaryInfo2 { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: small; 
        color: #000000; 
        background-color: #FFFFFF; 
        text-align: center; 
    } 
    .srvrSummaryFail { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: small; 
        color: #FFFFFF; 
        background-color: #E81123; 
        text-align: center; 
    } 
    .srvrSummaryPass { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-size: small; 
        color: #FFFFFF; 
        background-color: #107C10; 
        text-align: center; 
    } 
    p { 
        padding: 50px; 
    } 
    hr { 
        width: 100%; 
        max-width: 1280px; 
    } 
    table { 
        width: 100%; 
        max-width: 1280px; 
        border: 7px #505050; 
        border-collapse: collapse; 
    } 
    th, td, tr { 
        border: 2px black solid; 
    } 
    td#detail { 
        padding-left: 5px; 
    } 
    th#detail { 
        padding-right: 5px; 
    } 
    h1 { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-weight: bold; 
        font-size: xx-large; 
    } 
    h4 { 
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; 
        font-weight: bold; 
        font-size: medium; 
    } 
</style> 
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"> 
<head> 
    <meta content="en-us" http-equiv="Content-Language" /> 
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> 
    <title>Exchange JetStress Status Matrix</title> 
    <!--[if gte mso 9]><xml> 
    <mso:CustomDocumentProperties> 
    <mso:IsMyDocuments msdt:dt="string">1</mso:IsMyDocuments> 
    </mso:CustomDocumentProperties> 
    </xml><![endif]--> 
</head> 
<body> 
    <h1><a name="top">Exchange JetStress Results Matrix</a></h1> 
    <h4>Created: #Date#</h4> 
    <table> 
        <thead> 
            <tr> 
                <th class="srvrSummaryHeading1">Server Name</th> 
                <th class="srvrSummaryHeading2">Result</th> 
                <th class="srvrSummaryHeading2">Date of Last Run</th> 
                <th class="srvrSummaryHeading2">Duration</th> 
                <th class="srvrSummaryHeading2">Achieved IOPS</th> 
            </tr> 
        </thead> 
        <tbody> 
            #SummaryOutput# 
        </tbody> 
    </table> 
 
    #DetailedOutput# 
 
</body> 
</html> 
"@ 
sleep -Milliseconds 1750 
Write-Host ("Done!"-ForegroundColor Green 
 
$jsResults = $jsResults -replace "#Date#",$Date 
$jsResults = $jsResults -replace "#SummaryOutput#",$SummaryOutput 
$jsResults = $jsResults -replace "#DetailedOutput#",$DetailedOutput 
 
$jsResults | Out-File $OutputHTML 
#endregion 
# Create CSV file 
if ($CreateCSV) {$csvCollection | Export-Csv $OutputCSV -NoTypeInformation} 
 
Write-Host ("`n`r HTML Output can be found at: "-NoNewline 
Write-Host ("$ScriptPath\$($OutputHTML.SubString(2))"-ForegroundColor Yellow 
 
. $OutputHTML 
 
$StopWatch.Stop() 
Write-Host ("`n`r Script Completed in: {0} Days, {1} Hours, {2} Minutes, {3} Seconds`n`r" -$StopWatch.Elapsed.Days,$StopWatch.Elapsed.Hours,$StopWatch.Elapsed.Minutes,$StopWatch.Elapsed.Seconds) -ForegroundColor Yellow