From eddedd0b46f765ae93ec36ff17a3269b28483dd3 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Tue, 30 Jan 2024 14:29:26 -0600 Subject: [PATCH 1/8] Updated wording when EP is not configured --- ...nvoke-AnalyzerSecurityExtendedProtectionConfigState.ps1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityExtendedProtectionConfigState.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityExtendedProtectionConfigState.ps1 index b4f70b6d35..5b3d5bfe99 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityExtendedProtectionConfigState.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityExtendedProtectionConfigState.ps1 @@ -47,10 +47,13 @@ function Invoke-AnalyzerSecurityExtendedProtectionConfigState { # This combination means that EP is not configured and the Exchange build doesn't support it. # Recommended action: Upgrade to a supported build (Aug 2022 SU+) and enable EP afterwards. $epDetails = "Your Exchange server is at risk. Install the latest SU and enable Extended Protection" - } else { + } elseif ($extendedProtection.ExtendedProtectionConfigured) { # This means that EP is supported but not configured for at least one vDir. # Recommended action: Enable EP for each vDir on the system by using the script provided by us. - $epDetails += "Extended Protection isn't configured as expected" + $epDetails = "Extended Protection isn't configured as expected" + } else { + # No Extended Protection is configured, provide a slightly different wording to avoid confusion of possible misconfigured EP. + $epDetails = "Extended Protection is not configured" } $epCveParams = $baseParams + @{ From 5b5065e9a6cff386d32b1a3a99d20ae04b4c23fa Mon Sep 17 00:00:00 2001 From: David Paulson Date: Tue, 30 Jan 2024 15:18:26 -0600 Subject: [PATCH 2/8] HTML Report with date and provide location of output --- Diagnostics/HealthChecker/Features/Get-HtmlServerReport.ps1 | 2 ++ Diagnostics/HealthChecker/HealthChecker.ps1 | 2 +- docs/Diagnostics/HealthChecker/index.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Diagnostics/HealthChecker/Features/Get-HtmlServerReport.ps1 b/Diagnostics/HealthChecker/Features/Get-HtmlServerReport.ps1 index a07b4b7052..65dc7cb523 100644 --- a/Diagnostics/HealthChecker/Features/Get-HtmlServerReport.ps1 +++ b/Diagnostics/HealthChecker/Features/Get-HtmlServerReport.ps1 @@ -100,4 +100,6 @@ function Get-HtmlServerReport { $htmlReport = $htmlHeader + $htmlOverviewTable + $htmlServerDetails + "$([System.Environment]::NewLine)" $htmlReport | Out-File $HtmlOutFilePath -Encoding UTF8 + + Write-Host "HTML Report Location: $HtmlOutFilePath" } diff --git a/Diagnostics/HealthChecker/HealthChecker.ps1 b/Diagnostics/HealthChecker/HealthChecker.ps1 index e6018399b5..bef960c863 100644 --- a/Diagnostics/HealthChecker/HealthChecker.ps1 +++ b/Diagnostics/HealthChecker/HealthChecker.ps1 @@ -124,7 +124,7 @@ param( [switch]$BuildHtmlServersReport, [Parameter(Mandatory = $false, ParameterSetName = "HTMLReport", HelpMessage = "Provide the name of the Report to be created.")] - [string]$HtmlReportFile = "ExchangeAllServersReport.html", + [string]$HtmlReportFile = "ExchangeAllServersReport-$((Get-Date).ToString("yyyyMMddHHmmss")).html", [Parameter(Mandatory = $true, ParameterSetName = "DCCoreReport", HelpMessage = "Enable the DCCoreReport feature data collection against the current server's AD Site.")] [switch]$DCCoreRatio, diff --git a/docs/Diagnostics/HealthChecker/index.md b/docs/Diagnostics/HealthChecker/index.md index 7c2ba2a961..6a02ee8c99 100644 --- a/docs/Diagnostics/HealthChecker/index.md +++ b/docs/Diagnostics/HealthChecker/index.md @@ -152,7 +152,7 @@ ServerList | Used in combination with the LoadBalancingReport switch for letting SiteName | Used in combination with the LoadBalancingReport switch for letting the script to know which servers to run against in the site. XMLDirectoryPath | Used in combination with BuildHtmlServersReport switch for the location of the HealthChecker XML files for servers which you want to be included in the report. Default location is the current directory. BuildHtmlServersReport | Switch to enable the script to build the HTML report for all the servers XML results in the XMLDirectoryPath location. -HtmlReportFile | Name of the HTML output file from the BuildHtmlServersReport. Default is ExchangeAllServersReport.html +HtmlReportFile | Name of the HTML output file from the BuildHtmlServersReport. Default is ExchangeAllServersReport-yyyyMMddHHmmss.html DCCoreRatio | Gathers the Exchange to DC/GC Core ratio and displays the results in the current site that the script is running in. AnalyzeDataOnly | Switch to analyze the existing HealthChecker XML files. The results are displayed on the screen and an HTML report is generated. VulnerabilityReport | Switch to collect the Vulnerability Information for all the servers in the environment and export it out to json file. From a66ce63a4388948d99f8ce1bd6b785ac1c977f5e Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 2 Feb 2024 08:31:36 -0600 Subject: [PATCH 3/8] Get-FileContentInformation create in share --- Shared/Get-FileContentInformation.ps1 | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Shared/Get-FileContentInformation.ps1 diff --git a/Shared/Get-FileContentInformation.ps1 b/Shared/Get-FileContentInformation.ps1 new file mode 100644 index 0000000000..26416946c0 --- /dev/null +++ b/Shared/Get-FileContentInformation.ps1 @@ -0,0 +1,46 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\Invoke-ScriptBlockHandler.ps1 +function Get-FileContentInformation { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$ComputerName, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string[]]$FileLocation + ) + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $allFiles = New-Object System.Collections.Generic.List[string] + } + process { + foreach ($file in $FileLocation) { + $allFiles.Add($file) + } + } + end { + $params = @{ + ComputerName = $ComputerName + ScriptBlockDescription = "Getting File Content Information" + ArgumentList = @(, $allFiles) + ScriptBlock = { + param($FileLocations) + $results = @{} + foreach ($fileLocation in $FileLocations) { + $obj = [PSCustomObject]@{ + Present = ((Test-Path $fileLocation)) + FileName = ([IO.Path]::GetFileName($fileLocation)) + FilePath = $fileLocation + Content = (Get-Content $fileLocation -Raw) + } + + $results.Add($fileLocation, $obj) + } + return $results + } + } + return (Invoke-ScriptBlockHandler @params) + } +} From e117b5836287256ff86e1065985b3f29406002d2 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 2 Feb 2024 08:56:15 -0600 Subject: [PATCH 4/8] Use Get-FileContentInformation in share --- ...ke-AnalyzerFrequentConfigurationIssues.ps1 | 9 ++--- ...ApplicationConfigurationFileValidation.ps1 | 36 ------------------- .../Get-ExchangeInformation.ps1 | 8 ++--- 3 files changed, 9 insertions(+), 44 deletions(-) delete mode 100644 Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeApplicationConfigurationFileValidation.ps1 diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 index 6a41fd0056..0267ca064e 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 @@ -146,6 +146,7 @@ function Invoke-AnalyzerFrequentConfigurationIssues { foreach ($configKey in $keyList) { $configStatus = $exchangeInformation.ApplicationConfigFileStatus[$configKey] + $fileName = $configStatus.FileName $writeType = "Green" [string]$writeValue = $configStatus.Present @@ -155,12 +156,12 @@ function Invoke-AnalyzerFrequentConfigurationIssues { } $params = $baseParams + @{ - Name = "$configKey Present" + Name = "$fileName Present" Details = $writeValue DisplayWriteType = $writeType } - if ($alwaysDisplayConfigs -contains $configKey -or + if ($alwaysDisplayConfigs -contains $fileName -or -not $configStatus.Present) { Add-AnalyzedResultInformation @params } @@ -171,7 +172,7 @@ function Invoke-AnalyzerFrequentConfigurationIssues { $content = [xml]($configStatus.Content) # Additional checks of configuration files. - if ($configKey -eq "noderunner.exe.config") { + if ($fileName -eq "noderunner.exe.config") { $memoryLimitMegabytes = $content.configuration.nodeRunnerSettings.memoryLimitMegabytes $writeValue = "$memoryLimitMegabytes MB" $writeType = "Green" @@ -205,7 +206,7 @@ function Invoke-AnalyzerFrequentConfigurationIssues { } } catch { $params = $baseParams + @{ - Name = "$configKey Invalid Config Format" + Name = "$fileName Invalid Config Format" Details = "True --- Error: Not able to convert to xml which means it is in an incorrect format that will cause problems with the process." DisplayTestingValue = $true DisplayWriteType = "Red" diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeApplicationConfigurationFileValidation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeApplicationConfigurationFileValidation.ps1 deleted file mode 100644 index f521d543a5..0000000000 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeApplicationConfigurationFileValidation.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -. $PSScriptRoot\..\..\..\..\Shared\Invoke-ScriptBlockHandler.ps1 -function Get-ExchangeApplicationConfigurationFileValidation { - param( - [Parameter(Mandatory = $true)] - [string]$ComputerName, - - [string[]]$ConfigFileLocation - ) - Write-Verbose "Calling: $($MyInvocation.MyCommand)" - $results = @{} - $ConfigFileLocation | - ForEach-Object { - - $params = @{ - ComputerName = $ComputerName - ScriptBlockDescription = "Getting Exchange Application Configuration File Validation" - ArgumentList = $_ - ScriptBlock = { - param($Location) - return [PSCustomObject]@{ - Present = ((Test-Path $Location)) - FileName = ([IO.Path]::GetFileName($Location)) - FilePath = $Location - Content = (Get-Content $Location -Raw) - } - } - } - - $obj = Invoke-ScriptBlockHandler @params - $results.Add($obj.FileName, $obj) - } - return $results -} diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index b87ff32e25..0af73202e0 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -5,10 +5,10 @@ . $PSScriptRoot\..\..\..\..\Shared\ErrorMonitorFunctions.ps1 . $PSScriptRoot\..\..\..\..\Shared\Get-ExchangeBuildVersionInformation.ps1 . $PSScriptRoot\..\..\..\..\Shared\Get-ExchangeSettingOverride.ps1 +. $PSScriptRoot\..\..\..\..\Shared\Get-FileContentInformation.ps1 . $PSScriptRoot\IISInformation\Get-ExchangeAppPoolsInformation.ps1 . $PSScriptRoot\IISInformation\Get-ExchangeServerIISSettings.ps1 . $PSScriptRoot\Get-ExchangeAES256CBCDetails.ps1 -. $PSScriptRoot\Get-ExchangeApplicationConfigurationFileValidation.ps1 . $PSScriptRoot\Get-ExchangeConnectors.ps1 . $PSScriptRoot\Get-ExchangeDependentServices.ps1 . $PSScriptRoot\Get-ExchangeRegistryValues.ps1 @@ -98,12 +98,12 @@ function Get-ExchangeInformation { } $configParams = @{ - ComputerName = $Server - ConfigFileLocation = @("$([System.IO.Path]::Combine($serverExchangeBinDirectory, "EdgeTransport.exe.config"))", + ComputerName = $Server + FileLocation = @("$([System.IO.Path]::Combine($serverExchangeBinDirectory, "EdgeTransport.exe.config"))", "$([System.IO.Path]::Combine($serverExchangeBinDirectory, "Search\Ceres\Runtime\1.0\noderunner.exe.config"))") } - $applicationConfigFileStatus = Get-ExchangeApplicationConfigurationFileValidation @configParams + $applicationConfigFileStatus = Get-FileContentInformation @configParams $serverMaintenance = Get-ExchangeServerMaintenanceState -Server $Server -ComponentsToSkip "ForwardSyncDaemon", "ProvisioningRps" $settingOverrides = Get-ExchangeSettingOverride -Server $Server -CatchActionFunction ${Function:Invoke-CatchActions} From de290b4fd884e565f44d75934602809d1e7fd81c Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 7 Feb 2024 14:47:23 -0600 Subject: [PATCH 5/8] Remove IIS BE Binding Check --- .../Analyzer/Invoke-AnalyzerIISInformation.ps1 | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 index 8dd338e0a3..92c9947bca 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 @@ -105,13 +105,6 @@ function Invoke-AnalyzerIISInformation { $problemCertList.Add("'$certHash' Doesn't exist on the server and this will cause problems.") } elseif ($cert.LifetimeInDays -lt 0) { $problemCertList.Add("'$certHash' Has expired and will cause problems.") - } elseif ($_.bindingInformation -eq "*:444:") { - $namespaces = $cert.Namespaces | ForEach-Object { $_.ToString() } - - if ($namespaces -notcontains $exchangeInformation.GetExchangeServer.Fqdn -or - $namespaces -notcontains $exchangeInformation.GetExchangeServer.Name) { - $problemCertList.Add("'$certHash' Exchange Back End does not have hostname or FQDN for the namespaces. This can cause connectivity issues.") - } } } From 756c27140c45b4b6c2a2febafd068b0011a59973 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Tue, 30 Jan 2024 16:30:46 -0600 Subject: [PATCH 6/8] Address issue with export issue to xml Placed in a stop action and a try catch block. Also included a depth for ConvertTo-Json as that by default would only work 2 levels --- .../Features/Get-HealthCheckerData.ps1 | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Diagnostics/HealthChecker/Features/Get-HealthCheckerData.ps1 b/Diagnostics/HealthChecker/Features/Get-HealthCheckerData.ps1 index 0209fcfb30..ad7c15cfe7 100644 --- a/Diagnostics/HealthChecker/Features/Get-HealthCheckerData.ps1 +++ b/Diagnostics/HealthChecker/Features/Get-HealthCheckerData.ps1 @@ -123,19 +123,27 @@ function Get-HealthCheckerData { Write-Progress @paramWriteProgress try { - $analyzedResults | Export-Clixml -Path $Script:OutXmlFullPath -Encoding UTF8 -Depth 2 -ErrorAction SilentlyContinue + $analyzedResults | Export-Clixml -Path $Script:OutXmlFullPath -Encoding UTF8 -Depth 2 -ErrorAction Stop + Write-Verbose "Successfully export out the data" } catch { - Write-Verbose "Failed to Export-Clixml. Converting HealthCheckerExchangeServer to json" - $jsonHealthChecker = $analyzedResults.HealthCheckerExchangeServer | ConvertTo-Json - - $testOutputXml = [PSCustomObject]@{ - HealthCheckerExchangeServer = $jsonHealthChecker | ConvertFrom-Json - HtmlServerValues = $analyzedResults.HtmlServerValues - DisplayResults = $analyzedResults.DisplayResults + try { + Write-Verbose "Failed to Export-Clixml. Inner Exception: $_" + Write-Verbose "Converting HealthCheckerExchangeServer to json." + $jsonHealthChecker = $analyzedResults.HealthCheckerExchangeServer | ConvertTo-Json -Depth 6 -ErrorAction Stop + + $testOutputXml = [PSCustomObject]@{ + HealthCheckerExchangeServer = $jsonHealthChecker | ConvertFrom-Json -ErrorAction Stop + HtmlServerValues = $analyzedResults.HtmlServerValues + DisplayResults = $analyzedResults.DisplayResults + } + + $testOutputXml | Export-Clixml -Path $Script:OutXmlFullPath -Encoding UTF8 -Depth 2 -ErrorAction Stop + Write-Verbose "Successfully export out the data after the convert" + } catch { + Write-Red "Failed to Export-Clixml. Unable to export the data." } - - $testOutputXml | Export-Clixml -Path $Script:OutXmlFullPath -Encoding UTF8 -Depth 2 -ErrorAction Stop } finally { + # This prevents the need to call Invoke-CatchActions Invoke-ErrorCatchActionLoopFromIndex $currentErrors # for now don't want to display that we output the information if ReturnDataCollectionOnly is false From 82e3b68070bef13d3ec16b125f18079f1e53ed07 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 31 Jan 2024 15:31:40 -0600 Subject: [PATCH 7/8] Handle missing ExSetup on server --- .../Invoke-AnalyzerExchangeInformation.ps1 | 11 +++++++++++ .../ExchangeInformation/Get-ExSetupDetails.ps1 | 6 +++++- .../Get-ExchangeInformation.ps1 | 16 +++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 index b1162a0225..aa5b84f5ec 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerExchangeInformation.ps1 @@ -118,6 +118,17 @@ function Invoke-AnalyzerExchangeInformation { } } + # If the ExSetup wasn't found, we need to report that. + if ($exchangeInformation.BuildInformation.ExchangeSetup.FailedGetExSetup -eq $true) { + $params = $baseParams + @{ + Name = "Warning" + Details = "Didn't detect ExSetup.exe on the server. This might mean that setup didn't complete correctly the last time it was run." + DisplayCustomTabNumber = 2 + DisplayWriteType = "Yellow" + } + Add-AnalyzedResultInformation @params + } + if ($null -ne $exchangeInformation.BuildInformation.KBsInstalled) { Add-AnalyzedResultInformation -Name "Exchange IU or Security Hotfix Detected" @baseParams $problemKbFound = $false diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 index 58526e232d..52e69638d3 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 @@ -11,7 +11,11 @@ function Get-ExSetupDetails { Write-Verbose "Calling: $($MyInvocation.MyCommand)" $exSetupDetails = [string]::Empty function Get-ExSetupDetailsScriptBlock { - Get-Command ExSetup | ForEach-Object { $_.FileVersionInfo } + try { + Get-Command ExSetup -ErrorAction Stop | ForEach-Object { $_.FileVersionInfo } + } catch { + Write-Verbose "Failed to find ExSetup, need to fallback." + } } $exSetupDetails = Invoke-ScriptBlockHandler -ComputerName $Server -ScriptBlock ${Function:Get-ExSetupDetailsScriptBlock} -ScriptBlockDescription "Getting ExSetup remotely" -CatchActionFunction ${Function:Invoke-CatchActions} diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 index 0af73202e0..795fc6e928 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExchangeInformation.ps1 @@ -36,7 +36,21 @@ function Get-ExchangeInformation { $getExchangeServer = (Get-ExchangeServer -Identity $Server -Status) $exchangeCertificates = Get-ExchangeServerCertificates -Server $Server $exSetupDetails = Get-ExSetupDetails -Server $Server - $versionInformation = (Get-ExchangeBuildVersionInformation -FileVersion ($exSetupDetails.FileVersion)) + + if ($null -eq $exSetupDetails) { + # couldn't find ExSetup.exe this should be rare so we are just going to handle this by displaying the AdminDisplayVersion from Get-ExchangeServer + $versionInformation = (Get-ExchangeBuildVersionInformation -AdminDisplayVersion $getExchangeServer.AdminDisplayVersion) + $exSetupDetails = [PSCustomObject]@{ + FileVersion = $versionInformation.BuildVersion.ToString() + FileBuildPart = $versionInformation.BuildVersion.Build + FilePrivatePart = $versionInformation.BuildVersion.Revision + FileMajorPart = $versionInformation.BuildVersion.Major + FileMinorPart = $versionInformation.BuildVersion.Minor + FailedGetExSetup = $true + } + } else { + $versionInformation = (Get-ExchangeBuildVersionInformation -FileVersion ($exSetupDetails.FileVersion)) + } $buildInformation = [PSCustomObject]@{ ServerRole = (Get-ServerRole -ExchangeServerObj $getExchangeServer) From 50bb2a35097264b017de252d02aad4b7a9e4baa9 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 7 Feb 2024 15:00:18 -0600 Subject: [PATCH 8/8] Do a manual lookup for ExSetup.exe as well --- .../ExchangeInformation/Get-ExSetupDetails.ps1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 index 52e69638d3..ea82172f3f 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/Get-ExSetupDetails.ps1 @@ -14,7 +14,16 @@ function Get-ExSetupDetails { try { Get-Command ExSetup -ErrorAction Stop | ForEach-Object { $_.FileVersionInfo } } catch { - Write-Verbose "Failed to find ExSetup, need to fallback." + try { + Write-Verbose "Failed to find ExSetup by environment path locations. Attempting manual lookup." + $installDirectory = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup -ErrorAction Stop).MsiInstallPath + + if ($null -ne $installDirectory) { + Get-Command ([System.IO.Path]::Combine($installDirectory, "bin\ExSetup.exe")) -ErrorAction Stop | ForEach-Object { $_.FileVersionInfo } + } + } catch { + Write-Verbose "Failed to find ExSetup, need to fallback." + } } }