diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 index 575b25189e..08c89b7728 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 @@ -3,6 +3,7 @@ . $PSScriptRoot\Add-AnalyzedResultInformation.ps1 . $PSScriptRoot\Get-DisplayResultsGroupingKey.ps1 +. $PSScriptRoot\Invoke-AnalyzerKnownBuildIssues.ps1 Function Invoke-AnalyzerExchangeInformation { [CmdletBinding()] param( @@ -79,6 +80,10 @@ Function Invoke-AnalyzerExchangeInformation { } } + Invoke-AnalyzerKnownBuildIssues -AnalyzeResults $AnalyzeResults ` + -DisplayGroupingKey $keyExchangeInformation ` + -CurrentBuild $exchangeInformation.BuildInformation.ExchangeSetup.FileVersion + $AnalyzeResults | Add-AnalyzedResultInformation -Name "Server Role" -Details ($exchangeInformation.BuildInformation.ServerRole) ` -DisplayGroupingKey $keyExchangeInformation ` -AddHtmlOverviewValues $true diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 new file mode 100644 index 0000000000..a511d017fb --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerKnownBuildIssues.ps1 @@ -0,0 +1,168 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\Add-AnalyzedResultInformation.ps1 +. $PSScriptRoot\..\Helpers\Invoke-CatchActions.ps1 + +Function Invoke-AnalyzerKnownBuildIssues { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [ref]$AnalyzeResults, + + [Parameter(Mandatory = $true)] + [string]$CurrentBuild, + + [Parameter(Mandatory = $true)] + [object]$DisplayGroupingKey + ) + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + + # Extract for Pester Testing - Start + Function GetVersionFromString { + param( + [object]$VersionString + ) + try { + return New-Object System.Version $VersionString -ErrorAction Stop + } catch { + Write-Verbose "Failed to convert '$VersionString' in $($MyInvocation.MyCommand)" + Invoke-CatchActions + } + } + + Function GetKnownIssueInformation { + param( + [string]$Name, + [string]$Url + ) + + return [PSCustomObject]@{ + Name = $Name + Url = $Url + } + } + + Function GetKnownIssueBuildInformation { + param( + [string]$BuildNumber, + [string]$FixBuildNumber, + [bool]$BuildBound = $true + ) + + return [PSCustomObject]@{ + BuildNumber = $BuildNumber + FixBuildNumber = $FixBuildNumber + BuildBound = $BuildBound + } + } + + Function TestOnKnownBuildIssue { + [CmdletBinding()] + [OutputType("System.Boolean")] + param( + [object]$IssueBuildInformation, + [version]$CurrentBuild + ) + $knownIssue = GetVersionFromString $IssueBuildInformation.BuildNumber + Write-Verbose "Testing Known Issue Build $knownIssue" + + if ($null -eq $knownIssue -or + $CurrentBuild.Minor -ne $knownIssue.Minor) { return $false } + + $fixValueNull = [string]::IsNullOrEmpty($IssueBuildInformation.FixBuildNumber) + if ($fixValueNull) { + $resolvedBuild = GetVersionFromString "0.0.0.0" + } else { + $resolvedBuild = GetVersionFromString $IssueBuildInformation.FixBuildNumber + } + + Write-Verbose "Testing against possible resolved build number $resolvedBuild" + $buildBound = $IssueBuildInformation.BuildBound + $withinBuildBoundRange = $CurrentBuild.Build -eq $knownIssue.Build + $fixNeeded = $fixValueNull -or $CurrentBuild -lt $resolvedBuild + Write-Verbose "BuildBound: $buildBound | WithinBuildBoundRage: $withinBuildBoundRange | FixNeeded: $fixNeeded" + if ($CurrentBuild -ge $knownIssue) { + if ($buildBound) { + return $withinBuildBoundRange -and $fixNeeded + } else { + return $fixNeeded + } + } + + return $false + } + + # Extract for Pester Testing - End + + Function TestForKnownBuildIssues { + param( + [version]$CurrentVersion, + [object[]]$KnownBuildIssuesToFixes, + [object]$InformationUrl + ) + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + Write-Verbose "Testing CurrentVersion $CurrentVersion" + + if ($null -eq $Script:CachedKnownIssues) { + $Script:CachedKnownIssues = @() + } + + foreach ($issue in $KnownBuildIssuesToFixes) { + + if ((TestOnKnownBuildIssue $issue $CurrentVersion) -and + (-not($Script:CachedKnownIssues.Contains($InformationUrl)))) { + Write-Verbose "Known issue Match detected" + if (-not ($Script:DisplayKnownIssueHeader)) { + $Script:DisplayKnownIssueHeader = $true + + $AnalyzeResults | Add-AnalyzedResultInformation -Name "Known Issue Detected" ` + -Details "True" ` + -DisplayGroupingKey $DisplayGroupingKey ` + -DisplayWriteType "Yellow" + + $AnalyzeResults | Add-AnalyzedResultInformation -Details "This build has a known issue(s) which may or may not have been addressed. See the below link(s) for more information.`r`n" ` + -DisplayGroupingKey $DisplayGroupingKey ` + -DisplayCustomTabNumber 2 ` + -DisplayWriteType "Yellow" + } + + $AnalyzeResults | Add-AnalyzedResultInformation -Details "$($InformationUrl.Name):`r`n`t`t`t$($InformationUrl.Url)" ` + -DisplayGroupingKey $DisplayGroupingKey ` + -DisplayCustomTabNumber 2 ` + -DisplayWriteType "Yellow" + + if (-not ($Script:CachedKnownIssues.Contains($InformationUrl))) { + $Script:CachedKnownIssues += $InformationUrl + Write-Verbose "Added known issue to cache" + } + } + } + } + + try { + $currentVersion = New-Object System.Version $CurrentBuild -ErrorAction Stop + } catch { + Write-Verbose "Failed to set the current build to a version type object. $CurrentBuild" + Invoke-CatchActions + } + + try { + Write-Verbose "Working on November 2021 Security Updates - OWA redirection" + TestForKnownBuildIssues -CurrentVersion $currentVersion ` + -KnownBuildIssuesToFixes @( + (GetKnownIssueBuildInformation "15.2.986.14" $null), + (GetKnownIssueBuildInformation "15.2.922.19" $null), + (GetKnownIssueBuildInformation "15.1.2375.17" $null), + (GetKnownIssueBuildInformation "15.1.2308.20" $null), + (GetKnownIssueBuildInformation "15.0.1497.26" $null) + ) ` + -InformationUrl (GetKnownIssueInformation ` + "OWA redirection doesn't work after installing November 2021 security updates for Exchange Server 2019, 2016, or 2013" ` + "https://support.microsoft.com/help/5008997") + } catch { + Write-Verbose "Failed to run TestForKnownBuildIssues" + Invoke-CatchActions + } +} diff --git a/Diagnostics/HealthChecker/Analyzer/Tests/Invoke-AnalyzerKnownBuildIssues.Tests.ps1 b/Diagnostics/HealthChecker/Analyzer/Tests/Invoke-AnalyzerKnownBuildIssues.Tests.ps1 new file mode 100644 index 0000000000..5c6c17d6c2 --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Tests/Invoke-AnalyzerKnownBuildIssues.Tests.ps1 @@ -0,0 +1,75 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'Pester testing file')] +[CmdletBinding()] +param() + +BeforeAll { + . $PSScriptRoot\..\..\..\..\Shared\PesterLoadFunctions.NotPublished.ps1 + $scriptContent = Get-PesterScriptContent -FilePath "$PSScriptRoot\..\Invoke-AnalyzerKnownBuildIssues.ps1" + Invoke-Expression $scriptContent + Function Invoke-CatchActions { throw "Called Invoke-CatchActions" } + + Function TestPesterResults { + param( + [hashtable]$TestGroup, + [object]$KnownIssue + ) + + foreach ($key in $TestGroup.Keys) { + $currentBuild = GetVersionFromString $key + TestOnKnownBuildIssue $KnownIssue $currentBuild -Verbose | Should -Be $TestGroup[$key] + } + } +} + +Describe "Testing Known Build Issue Main Logic" { + + Context "Basic Test Initial Tests" { + + It "Initial Testing CU Bound" { + + TestPesterResults -TestGroup @{ + "15.1.2375.17" = $true + "15.1.2375.16" = $false + "15.1.2376.17" = $false + "15.1.2375.18" = $true + } ` + -KnownIssue (GetKnownIssueBuildInformation -BuildNumber "15.1.2375.17" -FixBuildNumber $null) + } + + It "Initial Testing CU Not Bound" { + + TestPesterResults -TestGroup @{ + "15.1.2375.17" = $true + "15.1.2375.16" = $false + "15.1.2376.17" = $true + "15.1.2375.18" = $true + } ` + -KnownIssue (GetKnownIssueBuildInformation -BuildNumber "15.1.2375.17" -FixBuildNumber $null -BuildBound $false) + } + + It "On Fix Build" { + + TestPesterResults -TestGroup @{ + "15.1.2375.17" = $true + "15.1.2375.16" = $false + "15.1.2376.17" = $false + "15.1.2375.18" = $false + } ` + -KnownIssue (GetKnownIssueBuildInformation -BuildNumber "15.1.2375.17" -FixBuildNumber "15.1.2375.18") + } + + It "Testing Major Diff" { + + TestPesterResults -TestGroup @{ + "15.1.2375.17" = $false + "15.1.2375.16" = $false + "15.1.2376.17" = $false + "15.1.2375.18" = $false + } ` + -KnownIssue (GetKnownIssueBuildInformation -BuildNumber "15.2.2375.17" -FixBuildNumber $null) + } + } +} diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Tests.ps1 index 2207a43674..3f1c89169a 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Tests.ps1 @@ -3,12 +3,6 @@ [CmdletBinding()] param() -BeforeAll { - . $PSScriptRoot\..\..\..\.build\BuildFunctions\Get-ExpandedScriptContent.ps1 - . $PSScriptRoot\..\Helpers\Class.ps1 - $Script:parentPath = (Split-Path -Parent $PSScriptRoot) - $Script:PesterExtract = "# Extract for Pester Testing - Start" -} Describe "Testing Health Checker by Mock Data Imports - Exchange 2013" { diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Tests.ps1 index 6c823e6a57..876d3fe831 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Tests.ps1 @@ -3,12 +3,6 @@ [CmdletBinding()] param() -BeforeAll { - . $PSScriptRoot\..\..\..\.build\BuildFunctions\Get-ExpandedScriptContent.ps1 - . $PSScriptRoot\..\Helpers\Class.ps1 - $Script:parentPath = (Split-Path -Parent $PSScriptRoot) - $Script:PesterExtract = "# Extract for Pester Testing - Start" -} Describe "Testing Health Checker by Mock Data Imports - Exchange 2016" { diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.Tests.ps1 index b5114666ad..5a7d365cbf 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.Tests.ps1 @@ -5,12 +5,6 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlets', '', Justification = 'Pester testing file')] [CmdletBinding()] param() -BeforeAll { - . $PSScriptRoot\..\..\..\.build\BuildFunctions\Get-ExpandedScriptContent.ps1 - . $PSScriptRoot\..\Helpers\Class.ps1 - $Script:parentPath = (Split-Path -Parent $PSScriptRoot) - $Script:PesterExtract = "# Extract for Pester Testing - Start" -} Describe "Testing Health Checker by Mock Data Imports" { diff --git a/Diagnostics/HealthChecker/Tests/HealthCheckerTests.ImportCode.NotPublished.ps1 b/Diagnostics/HealthChecker/Tests/HealthCheckerTests.ImportCode.NotPublished.ps1 index 0df54c423f..7ca37e581e 100644 --- a/Diagnostics/HealthChecker/Tests/HealthCheckerTests.ImportCode.NotPublished.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthCheckerTests.ImportCode.NotPublished.ps1 @@ -6,30 +6,15 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'Pester testing file')] [CmdletBinding()] param() +$Script:parentPath = (Split-Path -Parent $PSScriptRoot) +. $PSScriptRoot\..\Helpers\Class.ps1 +. $PSScriptRoot\..\..\..\Shared\PesterLoadFunctions.NotPublished.ps1 +$scriptContent = Get-PesterScriptContent -FilePath @( + "$Script:parentPath\Analyzer\Invoke-AnalyzerEngine.ps1", + "$Script:parentPath\DataCollection\ExchangeInformation\Get-HealthCheckerExchangeServer.ps1" +) -$scriptContent = Get-ExpandedScriptContent -File "$Script:parentPath\Analyzer\Invoke-AnalyzerEngine.ps1" -$scriptContentString = [string]::Empty -$scriptContent | ForEach-Object { $scriptContentString += "$($_)`n" } -Invoke-Expression $scriptContentString - -$internalFunctions = New-Object 'System.Collections.Generic.List[string]' -$scriptContent = Get-ExpandedScriptContent -File "$Script:parentPath\DataCollection\ExchangeInformation\Get-HealthCheckerExchangeServer.ps1" -$startIndex = $scriptContent.Trim().IndexOf($Script:PesterExtract) -for ($i = $startIndex + 1; $i -lt $scriptContent.Count; $i++) { - if ($scriptContent[$i].Trim().Contains($Script:PesterExtract.Replace("Start", "End"))) { - $endIndex = $i - break - } - $internalFunctions.Add($scriptContent[$i]) -} - -$scriptContent.RemoveRange($startIndex, $endIndex - $startIndex) -$scriptContentString = [string]::Empty -$internalFunctionsString = [string]::Empty -$scriptContent | ForEach-Object { $scriptContentString += "$($_)`n" } -$internalFunctions | ForEach-Object { $internalFunctionsString += "$($_)`n" } -Invoke-Expression $scriptContentString -Invoke-Expression $internalFunctionsString +Invoke-Expression $scriptContent Function SetActiveDisplayGrouping { [CmdletBinding()] diff --git a/Setup/Tests/SetupAssist.Tests.ps1 b/Setup/Tests/SetupAssist.Tests.ps1 index 9a8279a89b..eb0f7e96af 100644 --- a/Setup/Tests/SetupAssist.Tests.ps1 +++ b/Setup/Tests/SetupAssist.Tests.ps1 @@ -6,9 +6,8 @@ param() BeforeAll { - . $PSScriptRoot\..\..\.build\BuildFunctions\Get-ExpandedScriptContent.ps1 $Script:parentPath = [IO.Path]::Combine((Split-Path -Parent $PSScriptRoot), "SetupAssist") - $Script:PesterExtract = "# Extract for Pester Testing - Start" + . $PSScriptRoot\..\..\Shared\PesterLoadFunctions.NotPublished.ps1 } Describe "Testing SetupAssist" { @@ -16,24 +15,8 @@ Describe "Testing SetupAssist" { BeforeAll { #Load the functions - $internalFunctions = New-Object 'System.Collections.Generic.List[string]' - $scriptContent = Get-ExpandedScriptContent -File "$Script:parentPath\Checks\Domain\Test-ExchangeADSetupLevel.ps1" - $startIndex = $scriptContent.Trim().IndexOf($Script:PesterExtract) - for ($i = $startIndex + 1; $i -lt $scriptContent.Count; $i++) { - if ($scriptContent[$i].Trim().Contains($Script:PesterExtract.Replace("Start", "End"))) { - $endIndex = $i - break - } - $internalFunctions.Add($scriptContent[$i]) - } - - $scriptContent.RemoveRange($startIndex, $endIndex - $startIndex) - $scriptContentString = [string]::Empty - $internalFunctionsString = [string]::Empty - $scriptContent | ForEach-Object { $scriptContentString += "$($_)`n" } - $internalFunctions | ForEach-Object { $internalFunctionsString += "$($_)`n" } - Invoke-Expression $scriptContentString - Invoke-Expression $internalFunctionsString + $scriptContent = Get-PesterScriptContent -FilePath "$Script:parentPath\Checks\Domain\Test-ExchangeADSetupLevel.ps1" + Invoke-Expression $scriptContent } Context "Test-ExchangeADSetupLevel Function Test" { diff --git a/Shared/PesterLoadFunctions.NotPublished.ps1 b/Shared/PesterLoadFunctions.NotPublished.ps1 new file mode 100644 index 0000000000..e854daf64f --- /dev/null +++ b/Shared/PesterLoadFunctions.NotPublished.ps1 @@ -0,0 +1,46 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Common code to get the string value to run Invoke-Expression against. +# Need to return the string value as we can't run Invoke-Expression from inside this function as it won't expose it to the caller +Function Get-PesterScriptContent { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingInvokeExpression', '', Justification = 'Pester testing file')] + [CmdletBinding()] + [OutputType("System.String")] + param( + [string[]]$FilePath + ) + . $PSScriptRoot\..\.build\BuildFunctions\Get-ExpandedScriptContent.ps1 + $pesterExtract = "# Extract for Pester Testing - Start" + $scriptContentString = [string]::Empty + + foreach ($file in $FilePath) { + + $scriptContent = Get-ExpandedScriptContent -File $file + $internalFunctions = New-Object 'System.Collections.Generic.List[string]' + + while ($true) { + $startIndex = $scriptContent.Trim().IndexOf($pesterExtract) + + if ($startIndex -eq -1) { break } + + for ($i = $startIndex + 1; $i -lt $scriptContent.Count; $i++) { + if ($scriptContent[$i].Trim().Contains($pesterExtract.Replace("Start", "End"))) { + $endIndex = $i + break + } + + $internalFunctions.Add($scriptContent[$i]) + } + $scriptContent.RemoveRange($startIndex, $endIndex - $startIndex) + } + + $scriptContent | ForEach-Object { $scriptContentString += "$($_)`n" } + + if ($internalFunctions.Count -gt 0) { + $internalFunctions | ForEach-Object { $scriptContentString += "$($_)`n" } + } + } + + return $scriptContentString +}