Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,46 @@ function Add-AnalyzedResultInformation {
begin {
Write-Verbose "Calling $($MyInvocation.MyCommand): $name"

# Extract for Pester Testing - Start
function GetHtmlTextValue {
param(
[string]$OriginalValue
)

# Test for all the changes, if they do not exist, just return now.
if ([string]::IsNullOrEmpty($OriginalValue) -or
($OriginalValue.Contains(">") -eq $false -and
$OriginalValue.Contains("<") -eq $false)) {
if ([string]::IsNullOrEmpty($OriginalValue)) {
return $OriginalValue
}
Write-Verbose "Need to make changes for HTML text"
Write-Verbose "Original Value: $OriginalValue"
$OriginalValue = $OriginalValue.Replace(">", "&gt;")
$OriginalValue = $OriginalValue.Replace("<", "&lt;")
Write-Verbose "New Value: $OriginalValue"

# HTML encode < and > characters so they are not interpreted as HTML tags.
if ($OriginalValue.Contains("<") -or $OriginalValue.Contains(">")) {
Write-Verbose "Need to make changes for HTML text"
Write-Verbose "Original Value: $OriginalValue"
$OriginalValue = $OriginalValue.Replace(">", "&gt;")
$OriginalValue = $OriginalValue.Replace("<", "&lt;")
# Restore intentional <br> tags used for line breaks in multi-value HTML cells.
$OriginalValue = $OriginalValue.Replace("&lt;br&gt;", "<br>")
Write-Verbose "New Value: $OriginalValue"
}

# Convert URLs to clickable hyperlinks in the HTML report.
if ($OriginalValue.Contains("https://") -or $OriginalValue.Contains("http://")) {
$OriginalValue = [regex]::Replace($OriginalValue, '(https?://[^\s<>"''`]+)', {
param($match)
$url = $match.Groups[1].Value
# Strip trailing punctuation that is likely sentence-ending, not part of the URL.
$trailing = ""
while ($url.Length -gt 0 -and $url[-1] -match '[.,;)\]:]') {
$trailing = $url[-1] + $trailing
$url = $url.Substring(0, $url.Length - 1)
}
# cspell:ignore noopener noreferrer
return "<a href=`"$url`" target=`"_blank`" rel=`"noopener noreferrer`">$url</a>$trailing"
})
}

return $OriginalValue
}
# Extract for Pester Testing - End
function GetOutColumnsColorObject {
param(
[object[]]$OutColumns,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function Invoke-AnalyzerSecurityADV24199947 {
DisplayWriteType = "Red"
Details = "{0}"
DisplayTestingValue = "ADV24199947"
AddHtmlDetailRow = $false
}

if ($SecurityObject.IsEdgeServer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function Invoke-AnalyzerSecurityCve-2022-21978 {
Details = $null
DisplayWriteType = $null
DisplayTestingValue = "CVE-2022-21978"
AddHtmlDetailRow = $false
}
if ($null -ne $cveResults -or
$cveResults.Count -gt 0) {
Comment thread
dpaulson45 marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ function Invoke-AnalyzerSecurityCve-2022-41040 {
Details = $details
DisplayWriteType = "Red"
DisplayTestingValue = "CVE-2022-41040"
AddHtmlDetailRow = $false
}
Add-AnalyzedResultInformation @params
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function Invoke-AnalyzerSecurityCveAddressedBySerializedDataSigning {
DisplayGroupingKey = $DisplayGroupingKey
Name = "Security Vulnerability"
DisplayWriteType = "Red"
AddHtmlDetailRow = $false
}

$detailsString = "{0}`r`n`t`tSee: https://portal.msrc.microsoft.com/security-guidance/advisory/{0} for more information."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function Invoke-AnalyzerSecurityCveAndOverrideCheck {
Details = ("{0}$(if($overrideDisabled){" - Disabled By Override"})`r`n`t`tSee: https://portal.msrc.microsoft.com/security-guidance/advisory/{0} for more information." -f $CVEName)
DisplayWriteType = "Red"
DisplayTestingValue = $CVEName
AddHtmlDetailRow = $false
}
Add-AnalyzedResultInformation @params
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function Invoke-AnalyzerSecurityExtendedProtectionConfigState {
TestingName = "Extended Protection Vulnerable"
CustomName = $cveList
DisplayTestingValue = $true
AddHtmlDetailRow = $false
}
$epBasicParams = $baseParams + @{
DisplayWriteType = "Red"
Expand Down Expand Up @@ -132,6 +133,7 @@ function Invoke-AnalyzerSecurityExtendedProtectionConfigState {

$epFrontEndParams = $baseParams + @{
Name = "Security Vulnerability"
AddHtmlDetailRow = $false
OutColumns = ([PSCustomObject]@{
DisplayObject = $epFrontEndOutputObjectDisplayValue
ColorizerFunctions = @($epConfig)
Expand All @@ -142,6 +144,7 @@ function Invoke-AnalyzerSecurityExtendedProtectionConfigState {

$epBackEndParams = $baseParams + @{
Name = "Security Vulnerability"
AddHtmlDetailRow = $false
OutColumns = ([PSCustomObject]@{
DisplayObject = $epBackEndOutputObjectDisplayValue
ColorizerFunctions = @($epConfig)
Expand Down Expand Up @@ -179,6 +182,7 @@ function Invoke-AnalyzerSecurityExtendedProtectionConfigState {
TestingName = "Extended Protection Vulnerable"
CustomName = $cveList
DisplayTestingValue = $true
AddHtmlDetailRow = $false
}
Add-AnalyzedResultInformation @params
} else {
Expand Down
137 changes: 137 additions & 0 deletions Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,69 @@ Describe "Testing Health Checker by Mock Data Imports" {
. $PSScriptRoot\HealthCheckerTest.CommonMocks.NotPublished.ps1
}

Context "GetHtmlTextValue Unit Tests" {

It "Should return null for null input" {
$result = GetHtmlTextValue -OriginalValue $null
$result | Should -BeNullOrEmpty
}

It "Should return empty string for empty input" {
$result = GetHtmlTextValue -OriginalValue ""
$result | Should -Be ""
}

It "Should return plain text unchanged" {
$result = GetHtmlTextValue -OriginalValue "Exchange 2019 CU11"
$result | Should -Be "Exchange 2019 CU11"
}

It "Should encode angle brackets for certificate SAN values" {
$result = GetHtmlTextValue -OriginalValue "<SAN>CN=mail.contoso.com</SAN>"
$result | Should -Be "&lt;SAN&gt;CN=mail.contoso.com&lt;/SAN&gt;"
}

It "Should encode greater-than sign" {
$result = GetHtmlTextValue -OriginalValue "Value > 100"
$result | Should -Be "Value &gt; 100"
}

It "Should encode less-than sign" {
$result = GetHtmlTextValue -OriginalValue "Value < 100"
$result | Should -Be "Value &lt; 100"
}

It "Should handle mixed content with angle brackets and normal text" {
$result = GetHtmlTextValue -OriginalValue "Status: <Unknown> - Check docs"
$result | Should -Be "Status: &lt;Unknown&gt; - Check docs"
}

It "Should preserve intentional br tags after encoding" {
$testValue = "CVE-2020-1147<br>CVE-2023-36434<br>"
$result = GetHtmlTextValue -OriginalValue $testValue
$result | Should -Be "CVE-2020-1147<br>CVE-2023-36434<br>"
}

It "Should convert URLs to clickable hyperlinks" {
$result = GetHtmlTextValue -OriginalValue "More Information: https://aka.ms/HC-ExBuilds"
# cspell:ignore noopener noreferrer
$result | Should -BeLike '*<a href="https://aka.ms/HC-ExBuilds"*>https://aka.ms/HC-ExBuilds</a>'
}

It "Should convert URLs with trailing sentence punctuation" {
$result = GetHtmlTextValue -OriginalValue "See: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2020-1147 for more information."
$result | Should -BeLike '*<a href=*>https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2020-1147</a> for more information.'
}

It "Should handle br tags combined with URLs in security vulnerability summary" {
$testValue = "CVE-2020-1147`r`n`t`tSee: https://portal.msrc.microsoft.com/security-guidance/advisory/CVE-2020-1147 for more information.<br>"
$result = GetHtmlTextValue -OriginalValue $testValue
$result | Should -BeLike "*<br>*"
$result | Should -Not -BeLike "*&lt;br&gt;*"
$result | Should -BeLike "*<a href=*>*</a>*"
}
}

Context "Basic Exchange 2019 CU11 Testing HyperV" {
BeforeAll {
SetDefaultRunOfHealthChecker "Debug_HyperV_Results.xml"
Expand Down Expand Up @@ -192,6 +255,80 @@ Describe "Testing Health Checker by Mock Data Imports" {
$globalRulesWarning = GetObject "Global IIS Rewrite Rules"
$globalRulesWarning | Should -Not -BeNullOrEmpty
}

It "HTML Report - HtmlServerValues Structure" {
$Script:results.HtmlServerValues.ContainsKey("ServerDetails") | Should -Be $true
$Script:results.HtmlServerValues.ContainsKey("OverviewValues") | Should -Be $true
$Script:results.HtmlServerValues["ServerDetails"].Count | Should -BeGreaterThan 0
$Script:results.HtmlServerValues["OverviewValues"].Count | Should -BeGreaterThan 0

$firstDetail = $Script:results.HtmlServerValues["ServerDetails"][0]
$firstDetail.PSObject.Properties.Name | Should -Contain "Name"
$firstDetail.PSObject.Properties.Name | Should -Contain "DetailValue"
$firstDetail.PSObject.Properties.Name | Should -Contain "TableValue"
$firstDetail.PSObject.Properties.Name | Should -Contain "Class"

$firstOverview = $Script:results.HtmlServerValues["OverviewValues"][0]
$firstOverview.PSObject.Properties.Name | Should -Contain "Name"
$firstOverview.PSObject.Properties.Name | Should -Contain "DetailValue"

# ServerDetails captures most entries while OverviewValues is selective
$Script:results.HtmlServerValues["ServerDetails"].Count |
Should -BeGreaterThan $Script:results.HtmlServerValues["OverviewValues"].Count
}

It "HTML Report - Overview Values" {
$serverName = GetHtmlOverviewValue "Server Name"
$serverName | Should -Not -BeNullOrEmpty
$serverName.DetailValue | Should -Not -BeNullOrEmpty

$exchangeVersion = GetHtmlOverviewValue "Exchange Version"
$exchangeVersion | Should -Not -BeNullOrEmpty
$exchangeVersion.DetailValue | Should -Not -BeNullOrEmpty

$generationTime = GetHtmlOverviewValue "Generation Time"
$generationTime | Should -Not -BeNullOrEmpty

$vulnDetected = GetHtmlOverviewValue "Vulnerability Detected"
$vulnDetected | Should -Not -BeNullOrEmpty
if ($vulnDetected.DetailValue -ne "None") {
$vulnDetected.Class | Should -Be "Red"
}
}

It "HTML Report - ServerDetails CSS Class Mapping" {
# Grey write type → empty Class
$serverName = GetHtmlServerDetail "Server Name"
$serverName | Should -Not -BeNullOrEmpty
$serverName.Class | Should -BeNullOrEmpty

# Yellow write type → Yellow Class
$edition = GetHtmlServerDetail "Edition"
if ($null -ne $edition) {
$edition.Class | Should -Be "Yellow"
}
}

It "HTML Report - Security Vulnerabilities HTML Rendering" {
# Individual "Security Vulnerability" entries should NOT appear in ServerDetails.
# They are rolled up into the "Security Vulnerabilities" summary row.
# The regular CVE path sets AddHtmlDetailRow = $false, but the override CVE path
# in Invoke-AnalyzerSecurityCveAndOverrideCheck.ps1 is missing it.
Comment on lines +315 to +316
$individualCveEntries = $Script:results.HtmlServerValues["ServerDetails"] |
Where-Object { $_.Name -eq "Security Vulnerability" }
$individualCveEntries | Should -BeNullOrEmpty

# The summary row should be present
$entry = GetHtmlServerDetail "Security Vulnerabilities"
$entry | Should -Not -BeNullOrEmpty

if ($null -ne $entry -and -not [string]::IsNullOrEmpty($entry.DetailValue)) {
$entry.DetailValue | Should -BeLike "*CVE-*"
# Validates the fix for the PR #2475 regression: <br> tags must be preserved
$entry.DetailValue | Should -Not -BeLike "*&lt;br&gt;*"
$entry.DetailValue | Should -BeLike "*<br>*"
}
}
}

Context "Basic Exchange 2019 CU11 Testing Physical" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,24 @@ function TestObjectMatch {
Should -Be $WriteType
}

function GetHtmlServerDetail {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 1)]
[string]$Name
)
$Script:results.HtmlServerValues["ServerDetails"] | Where-Object { $_.Name -eq $Name }
}

function GetHtmlOverviewValue {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 1)]
[string]$Name
)
$Script:results.HtmlServerValues["OverviewValues"] | Where-Object { $_.Name -eq $Name }
}

function TestOutColumnObjectCompare {
[CmdletBinding()]
param(
Expand Down