From 752557f82205e268fd027667eeaa2dc32a7b827d Mon Sep 17 00:00:00 2001 From: Shane Ferrell Date: Thu, 7 Dec 2023 12:07:52 -0800 Subject: [PATCH 01/15] Update Timeline so all logs are [Who] did what to Type with Client --- .../Get-CalendarDiagnosticObjectsSummary.ps1 | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 index 4d5892d96e..6b0f4ecc4d 100644 --- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 +++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 @@ -1050,14 +1050,14 @@ function BuildTimeline { switch ($CalLog.TriggerAction) { Create { if ($CalLog.IsOrganizer) { - if ($CalLog.IsException) { + if ($CalLog.IsException -eq $True) { $Output1 = "A new Exception $($CalLog.MeetingRequestType.Value) Meeting Request was created with $($CalLog.Client)" } else { $Output1 = "A new $($CalLog.MeetingRequestType.Value) Meeting Request was created with $($CalLog.Client)" } if ($CalLog.SentRepresentingEmailAddress -eq $CalLog.SenderEmailAddress) { - $Output2 = " by the Organizer $($CalLog.ResponsibleUser)." + $Output2 = " by the Organizer [$($CalLog.ResponsibleUser)]." } else { $Output2 = " by the Delegate." } @@ -1072,7 +1072,11 @@ function BuildTimeline { [array] $Output = "Transport delivered a new Meeting Request from $($CalLog.SentRepresentingDisplayName)." [bool] $MeetingSummaryNeeded = $True } else { - [array] $Output = "$($CalLog.ResponsibleUser) sent a $($CalLog.MeetingRequestType.Value) update for the Meeting Request and was processed by $($CalLog.Client)." + if ($CalLog.IsException -eq $True) { + [array] $Output = "[$($CalLog.ResponsibleUser)] Created a new Meeting Request with $($CalLog.Client) for an exception starting on [$($CalLog.StartTime)]." + } else { + [array] $Output = "[$($CalLog.ResponsibleUser)] Created a new Meeting Request with $($CalLog.Client)." + } } } } @@ -1084,11 +1088,11 @@ function BuildTimeline { if ($CalLog.ResponsibleUser -eq "Calendar Assistant") { [array] $Output = "$($CalLog.Client) Deleted the Meeting Request." } else { - [array] $Output = "$($CalLog.ResponsibleUser) Deleted the Meeting Request with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] Deleted the Meeting Request with $($CalLog.Client)." } } default { - [array] $Output = "$($CalLog.TriggerAction) was performed on the $($CalLog.MeetingRequestType) Meeting Request by $($CalLog.ResponsibleUser) with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] Deleted the $($CalLog.MeetingRequestType) Meeting Request with $($CalLog.Client)." } } } @@ -1100,14 +1104,14 @@ function BuildTimeline { } if ($CalLog.AppointmentCounterProposal -eq "True") { - [array] $Output = "$($CalLog.SentRepresentingDisplayName) send a $($MeetingRespType) response message with a New Time Proposal: $($CalLog.MapiStartTime) to $($CalLog.MapiEndTime)" + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] send a $($MeetingRespType) response message with a New Time Proposal: $($CalLog.MapiStartTime) to $($CalLog.MapiEndTime)" } else { switch -Wildcard ($CalLog.TriggerAction) { - "Update" { $Action = "updated" } - "Create" { $Action = "sent" } - "*Delete*" { $Action = "deleted" } + "Update" { $Action = "Updated" } + "Create" { $Action = "Sent" } + "*Delete*" { $Action = "Deleted" } default { - $Action = "update" + $Action = "Updated" } } @@ -1119,34 +1123,28 @@ function BuildTimeline { } if ($CalLog.IsOrganizer) { - [array] $Output = "$($CalLog.SentRepresentingDisplayName) $($Action) a $($MeetingRespType) Meeting Response message$($Extra)." + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] $($Action) a $($MeetingRespType) Meeting Response message$($Extra)." } else { switch ($CalLog.Client) { ResourceBookingAssistant { [array] $Output = "ResourceBookingAssistant $($Action) a $($MeetingRespType) Meeting Response message." } Transport { - [array] $Output = "$($CalLog.SentRepresentingDisplayName) $($Action) $($MeetingRespType) Meeting Response message." + [array] $Output = "[$($CalLog.From)] $($Action) $($MeetingRespType) Meeting Response message." } default { - [array] $Output = "Meeting Response $($MeetingRespType) from [$($CalLog.SentRepresentingDisplayName)] was $($Action) by $($CalLog.ResponsibleUser) with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] $($Action) [$($CalLog.SentRepresentingDisplayName)]'s $($MeetingRespType) Meeting Response with $($CalLog.Client)." } } } } } ForwardNotification { - [array] $Output = "The meeting was FORWARDED by $($CalLog.SentRepresentingDisplayName)." + [array] $Output = "The meeting was FORWARDED by [$($CalLog.SentRepresentingDisplayName)]." } ExceptionMsgClass { - if ($CalLog.TriggerAction -eq "Create") { - $Action = "New" - } else { - $Action = "$($CalLog.TriggerAction)" - } - if ($CalLog.ResponsibleUser -ne "Calendar Assistant") { - [array] $Output = "$($Action) Exception to the meeting series added by $($CalLog.ResponsibleUser) with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] $($CalLog.TriggerAction)d Exception to the meeting series with $($CalLog.Client)." } } IpmAppointment { @@ -1156,7 +1154,7 @@ function BuildTimeline { if ($CalLog.Client -eq "Transport") { [array] $Output = "Transport created a new meeting." } else { - [array] $Output = "$($CalLog.SentRepresentingDisplayName) created a new Meeting with $($CalLog.Client)." + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] created a new Meeting with $($CalLog.Client)." } } else { switch ($CalLog.Client) { @@ -1167,7 +1165,7 @@ function BuildTimeline { [array] $Output = "$($CalLog.Client) added a new Tentative Meeting from $($CalLog.SentRepresentingDisplayName) to the Calendar." } default { - [array] $Output = "Meeting was created by [$($CalLog.ResponsibleUser)] with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] created the Meeting with $($CalLog.Client)." } } } @@ -1175,7 +1173,7 @@ function BuildTimeline { Update { switch ($CalLog.Client) { Transport { - [array] $Output = "Transport $($CalLog.TriggerAction)d the meeting from $($CalLog.SentRepresentingDisplayName)." + [array] $Output = "Transport $($CalLog.TriggerAction)d the meeting." } LocationProcessor { [array] $Output = "" @@ -1187,7 +1185,7 @@ function BuildTimeline { if ($CalLog.ResponsibleUser -eq "Calendar Assistant") { [array] $Output = "The Exchange System $($CalLog.TriggerAction)d the meeting via the Calendar Assistant." } else { - [array] $Output = "$($CalLog.TriggerAction) to the Meeting by [$($CalLog.ResponsibleUser)] with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] $($CalLog.TriggerAction)d the Meeting with $($CalLog.Client)." $AddChangedProperties = $True } } @@ -1197,7 +1195,7 @@ function BuildTimeline { if ($CalLog.ResponsibleUserName -eq "Calendar Assistant") { [array] $Output = "$($CalLog.Client) Accepted the meeting." } else { - [array] $Output = "$($CalLog.ResponsibleUser) Accepted the meeting with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] Accepted the meeting with $($CalLog.Client)." } $AddChangedProperties = $False } elseif ($CalLog.FreeBusyStatus -ne 2 -and $PreviousCalLog.FreeBusyStatus -eq 2) { @@ -1208,29 +1206,29 @@ function BuildTimeline { SoftDelete { switch ($CalLog.Client) { Transport { - [array] $Output = "Transport $($CalLog.TriggerAction)d the Meeting from $($CalLog.SentRepresentingDisplayName)." + [array] $Output = "Transport $($CalLog.TriggerAction)d the Meeting." } LocationProcessor { [array] $Output = "" } ResourceBookingAssistant { - [array] $Output = "ResourceBookingAssistant $($CalLog.TriggerAction) the Meeting." + [array] $Output = "ResourceBookingAssistant $($CalLog.TriggerAction)d the Meeting." } default { if ($CalLog.ResponsibleUser -eq "Calendar Assistant") { - [array] $Output = "The Exchange System $($CalLog.TriggerAction)s the meeting via the Calendar Assistant." + [array] $Output = "The Exchange System $($CalLog.TriggerAction)d the meeting via the Calendar Assistant." } else { - [array] $Output = "The Meeting was $($CalLog.TriggerAction) by [$($CalLog.ResponsibleUser)] with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] $($CalLog.TriggerAction)d the meeting with $($CalLog.Client)." $AddChangedProperties = $True } } } if ($CalLog.FreeBusyStatus -eq 2 -and $PreviousCalLog.FreeBusyStatus -ne 2) { - [array] $Output = "The $($CalLog.ResponsibleUser) accepted the Meeting with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] Accepted the Meeting with $($CalLog.Client)." $AddChangedProperties = $False } elseif ($CalLog.FreeBusyStatus -ne 2 -and $PreviousCalLog.FreeBusyStatus -eq 2) { - [array] $Output = "The $($CalLog.ResponsibleUser) declined the Meeting with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] Declined the Meeting with $($CalLog.Client)." $AddChangedProperties = $False } } @@ -1249,7 +1247,13 @@ function BuildTimeline { [array] $Output = "Transport $($CalLog.TriggerAction)d the Meeting Cancellation from $($CalLog.SentRepresentingDisplayName)." } default { - [array] $Output = "$($CalLog.ResponsibleUser) $($CalLog.TriggerAction) the Cancellation with $($CalLog.Client)." + if ($CalLog.IsException -eq $True) { + [array] $Output = "[$($CalLog.ResponsibleUser)] $($CalLog.TriggerAction)d a Cancellation with $($CalLog.Client) for the exception starting on [$($CalLog.StartTime)]." + } elseif ($CalLog.CalendarItemType -eq "RecurringMaster") { + [array] $Output = "[$($CalLog.ResponsibleUser)] $($CalLog.TriggerAction)d a Cancellation for the Series with $($CalLog.Client)." + } else { + [array] $Output = "[$($CalLog.ResponsibleUser)] $($CalLog.TriggerAction)d the Cancellation with $($CalLog.Client)." + } } } } @@ -1259,7 +1263,7 @@ function BuildTimeline { } else { $Action = "$($CalLog.TriggerAction)" } - [array] $Output = "$($Action) was performed on the $($CalLog.ItemClass) by $($CalLog.ResponsibleUser) with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] perfomed a $($Action) on the $($CalLog.ItemClass) with $($CalLog.Client)." } } From 1f2530a1ccf8d6a85aa76a1a73cae552083a931e Mon Sep 17 00:00:00 2001 From: Shane Ferrell Date: Wed, 13 Dec 2023 14:44:46 -0800 Subject: [PATCH 02/15] Fix Spelling --- Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 index 6b0f4ecc4d..4d1a82667e 100644 --- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 +++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 @@ -1263,7 +1263,7 @@ function BuildTimeline { } else { $Action = "$($CalLog.TriggerAction)" } - [array] $Output = "[$($CalLog.ResponsibleUser)] perfomed a $($Action) on the $($CalLog.ItemClass) with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] performed a $($Action) on the $($CalLog.ItemClass) with $($CalLog.Client)." } } From 4ae077a507cb7f8bd5a635ab7f84a30052b3e29e Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Mon, 18 Dec 2023 18:11:11 +0100 Subject: [PATCH 03/15] Make the basic build process work on MacOS / Linux --- .build/Build.ps1 | 9 ++++++++- .gitignore | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.build/Build.ps1 b/.build/Build.ps1 index ee98dd59db..4116035b36 100644 --- a/.build/Build.ps1 +++ b/.build/Build.ps1 @@ -139,7 +139,14 @@ if ($nonUnique.Count -gt 0) { $scriptVersions = @() -$disclaimer = [IO.File]::ReadAllLines("$PSScriptRoot\..\LICENSE") +if ($IsMacOS -or + $IsLinux) { + Write-Host "We're running the build process on MacOS / Linux" + $disclaimer = [IO.File]::ReadAllLines("$((Get-Location).Path)/../LICENSE") +} else { + Write-Host "We're running the build process on Windows" + $disclaimer = [IO.File]::ReadAllLines("$PSScriptRoot\..\LICENSE") +} $documentedScriptFiles | ForEach-Object { $scriptName = [IO.Path]::GetFileName($_) diff --git a/.gitignore b/.gitignore index 1521c8b765..36d3a9c3ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ dist +.DS_Store From 0b233afc943dcf21ce921d62574c9944fbb6136d Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 16 Nov 2023 17:09:20 -0600 Subject: [PATCH 04/15] Moved all IIS analyzed settings to Invoke-AnalyzerIISInformation --- ...ke-AnalyzerFrequentConfigurationIssues.ps1 | 318 ---------------- .../Invoke-AnalyzerIISInformation.ps1 | 349 +++++++++++++++++- .../Tests/HealthChecker.E15.Main.Tests.ps1 | 3 +- .../Tests/HealthChecker.E16.Main.Tests.ps1 | 3 +- .../Tests/HealthChecker.E19.Main.Tests.ps1 | 22 +- .../HealthChecker.E19.Scenarios.Tests.ps1 | 74 ++-- .../HealthChecker.E19.Scenarios2.Tests.ps1 | 4 +- 7 files changed, 398 insertions(+), 375 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 index d68f5345e8..01f3180c80 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerFrequentConfigurationIssues.ps1 @@ -204,322 +204,4 @@ function Invoke-AnalyzerFrequentConfigurationIssues { Details = $exchangeInformation.RegistryValues.DisablePreservation } Add-AnalyzedResultInformation @params - - if ($null -ne $exchangeInformation.IISSettings.IISWebApplication -or - $null -ne $exchangeInformation.IISSettings.IISWebSite -or - $null -ne $exchangeInformation.IISSettings.IISSharedWebConfig) { - $iisWebSettings = @($exchangeInformation.IISSettings.IISWebApplication) - $iisWebSettings += @($exchangeInformation.IISSettings.IISWebSite) - $iisConfigurationSettings = @($exchangeInformation.IISSettings.IISWebApplication.ConfigurationFileInfo) - $iisConfigurationSettings += $iisWebSiteConfigs = @($exchangeInformation.IISSettings.IISWebSite.ConfigurationFileInfo) - $iisConfigurationSettings += @($exchangeInformation.IISSettings.IISSharedWebConfig) - - # Invalid configuration files are ones that we can't convert to xml. - $invalidConfigurationFile = $iisConfigurationSettings | Where-Object { $_.Valid -eq $false -and $_.Exist -eq $true } - # If a web application config file doesn't truly exists, we end up using the parent web.config file - # If any of the web application config file paths match a parent path, that is a problem. - # only collect the ones that are valid, if not valid we will assume that the child web apps will point to it and can be misleading. - $siteConfigPaths = $iisWebSiteConfigs | - Where-Object { $_.Valid -eq $true -and $_.Exist -eq $true } | - ForEach-Object { $_.Location.ToLower() } - - if ($null -ne $siteConfigPaths) { - $missingWebApplicationConfigFile = $exchangeInformation.IISSettings.IISWebApplication | - Where-Object { $siteConfigPaths.Contains($_.ConfigurationFileInfo.Location.ToLower()) } - } - - # Missing config file should really only occur for SharedWebConfig files, as the web application would go back to the parent site. - $missingSharedConfigFile = @($exchangeInformation.IISSettings.IISSharedWebConfig) | Where-Object { $_.Exist -eq $false } - $missingConfigFiles = $iisWebSettings | Where-Object { $_.ConfigurationFileInfo.Exist -eq $false } - $defaultVariableDetected = $iisConfigurationSettings | Where-Object { $null -ne ($_.Content | Select-String "%ExchangeInstallDir%") } - $binSearchFoldersNotFound = $iisConfigurationSettings | - Where-Object { $_.Location -like "*\ClientAccess\ecp\web.config" -and $_.Exist -eq $true -and $_.Valid -eq $true } | - Where-Object { - $binSearchFolders = (([xml]($_.Content)).configuration.appSettings.add | Where-Object { - $_.key -eq "BinSearchFolders" - }).value - $paths = $binSearchFolders.Split(";").Trim().ToLower() - $paths | ForEach-Object { Write-Verbose "BinSearchFolder: $($_)" } - $installPath = $exchangeInformation.RegistryValues.MsiInstallPath - foreach ($binTestPath in @("bin", "bin\CmdletExtensionAgents", "ClientAccess\Owa\bin")) { - $testPath = [System.IO.Path]::Combine($installPath, $binTestPath).ToLower() - Write-Verbose "Testing path: $testPath" - if (-not ($paths.Contains($testPath))) { - return $_ - } - } - } - $iisWebSitesWithHstsSettings = $iisWebSettings | Where-Object { $null -ne $_.hsts } - - if ($null -ne $missingWebApplicationConfigFile) { - $params = $baseParams + @{ - Name = "Missing Web Application Configuration File" - DisplayWriteType = "Red" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - - foreach ($webApp in $missingWebApplicationConfigFile) { - $params = $baseParams + @{ - Details = "Web Application: '$($webApp.FriendlyName)' Attempting to use config: '$($webApp.ConfigurationFileInfo.Location)'" - DisplayWriteType = "Red" - DisplayCustomTabNumber = 2 - TestingName = "Web Application: '$($webApp.FriendlyName)'" - DisplayTestingValue = $($webApp.ConfigurationFileInfo.Location) - } - Add-AnalyzedResultInformation @params - } - } - - if ($null -ne $invalidConfigurationFile) { - $params = $baseParams + @{ - Name = "Invalid Configuration File" - DisplayWriteType = "Red" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - - $alreadyDisplayConfigs = New-Object 'System.Collections.Generic.HashSet[string]' - foreach ($configFile in $invalidConfigurationFile) { - if ($alreadyDisplayConfigs.Add($configFile.Location)) { - $params = $baseParams + @{ - Details = "Invalid: $($configFile.Location)" - DisplayWriteType = "Red" - DisplayCustomTabNumber = 2 - TestingName = "Invalid: $($configFile.Location)" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - } - } - } - - if ($null -ne $missingSharedConfigFile) { - $params = $baseParams + @{ - Name = "Missing Shared Configuration File" - DisplayWriteType = "Red" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - - foreach ($file in $missingSharedConfigFile) { - $params = $baseParams + @{ - Details = "Missing: $($file.Location)" - DisplayWriteType = "Red" - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - $params = $baseParams + @{ - Details = "More Information: https://aka.ms/HC-MissingConfig" - DisplayWriteType = "Yellow" - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - if ($null -ne $missingConfigFiles) { - $params = $baseParams + @{ - Name = "Couldn't Find Config File" - DisplayWriteType = "Red" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - - foreach ($file in $missingConfigFiles) { - $params = $baseParams + @{ - Details = "Friendly Name: $($file.FriendlyName)" - DisplayWriteType = "Red" - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - } - - if ($null -ne $defaultVariableDetected) { - $params = $baseParams + @{ - Name = "Default Variable Detected" - DisplayWriteType = "Red" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - - foreach ($file in $defaultVariableDetected) { - $params = $baseParams + @{ - Details = "$($file.Location)" - DisplayWriteType = "Red" - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - $params = $baseParams + @{ - Details = "More Information: https://aka.ms/HC-DefaultVariableDetected" - DisplayWriteType = "Yellow" - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - if ($null -ne $binSearchFoldersNotFound) { - $params = $baseParams + @{ - Name = "Bin Search Folder Not Found" - DisplayWriteType = "Red" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - - foreach ($file in $binSearchFoldersNotFound) { - $params = $baseParams + @{ - Details = "$($file.Location)" - DisplayWriteType = "Red" - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - $params = $baseParams + @{ - Details = "More Information: https://aka.ms/HC-BinSearchFolder" - DisplayWriteType = "Yellow" - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - # TODO: Move this check to the new IIS section that we'll add to HC in near future - See issue: 1363 - if (($iisWebSitesWithHstsSettings.Hsts.NativeHstsSettings.enabled -notcontains $true) -and - ($iisWebSitesWithHstsSettings.Hsts.HstsViaCustomHeader.enabled -notcontains $true)) { - $params = $baseParams + @{ - Name = "HSTS Enabled" - Details = $false - } - Add-AnalyzedResultInformation @params - } else { - $showAdditionalHstsInformation = $false - foreach ($webSite in $iisWebSitesWithHstsSettings) { - $hstsConfiguration = $null - $isExchangeBackEnd = $webSite.Name -eq "Exchange Back End" - $hstsMaxAgeWriteType = "Green" - - if (($webSite.Hsts.NativeHstsSettings.enabled) -or - ($webSite.Hsts.HstsViaCustomHeader.enabled)) { - $params = $baseParams + @{ - Name = "HSTS Enabled" - Details = "$($webSite.Name)" - TestingName = "hsts-Enabled-$($webSite.Name)" - DisplayTestingValue = $true - DisplayWriteType = if ($isExchangeBackEnd) { "Red" } else { "Green" } - } - Add-AnalyzedResultInformation @params - - if ($isExchangeBackEnd) { - $showAdditionalHstsInformation = $true - $params = $baseParams + @{ - Details = "HSTS on 'Exchange Back End' is not supported and can cause issues" - DisplayWriteType = "Red" - TestingName = "hsts-BackendNotSupported" - DisplayTestingValue = $true - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - if (($webSite.Hsts.NativeHstsSettings.enabled) -and - ($webSite.Hsts.HstsViaCustomHeader.enabled)) { - $showAdditionalHstsInformation = $true - Write-Verbose "HSTS conflict detected" - $params = $baseParams + @{ - Details = ("HSTS configured via customHeader and native IIS control - please remove one configuration" + - "`r`n`t`tHSTS native IIS control has a higher weight than the customHeader and will be used") - DisplayWriteType = "Yellow" - TestingName = "hsts-conflict" - DisplayTestingValue = $true - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - - if ($webSite.Hsts.NativeHstsSettings.enabled) { - Write-Verbose "HSTS configured via native IIS control" - $hstsConfiguration = $webSite.Hsts.NativeHstsSettings - } else { - Write-Verbose "HSTS configured via customHeader" - $hstsConfiguration = $webSite.Hsts.HstsViaCustomHeader - } - - $maxAgeValue = $hstsConfiguration.'max-age' - if ($maxAgeValue -lt 31536000) { - $showAdditionalHstsInformation = $true - $hstsMaxAgeWriteType = "Yellow" - } - $params = $baseParams + @{ - Details = "max-age: $maxAgeValue" - DisplayWriteType = $hstsMaxAgeWriteType - TestingName = "hsts-max-age-$($webSite.Name)" - DisplayTestingValue = $maxAgeValue - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - - $params = $baseParams + @{ - Details = "includeSubDomains: $($hstsConfiguration.includeSubDomains)" - TestingName = "hsts-includeSubDomains-$($webSite.Name)" - DisplayTestingValue = $hstsConfiguration.includeSubDomains - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - - $params = $baseParams + @{ - Details = "preload: $($hstsConfiguration.preload)" - TestingName = "hsts-preload-$($webSite.Name)" - DisplayTestingValue = $hstsConfiguration.preload - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - - $redirectHttpToHttpsConfigured = $hstsConfiguration.redirectHttpToHttps - $params = $baseParams + @{ - Details = "redirectHttpToHttps: $redirectHttpToHttpsConfigured" - TestingName = "hsts-redirectHttpToHttps-$($webSite.Name)" - DisplayTestingValue = $redirectHttpToHttpsConfigured - DisplayCustomTabNumber = 2 - } - if ($redirectHttpToHttpsConfigured) { - $showAdditionalHstsInformation = $true - $params.Add("DisplayWriteType", "Red") - } - Add-AnalyzedResultInformation @params - } - } - - if ($showAdditionalHstsInformation) { - $params = $baseParams + @{ - Details = "`r`n`t`tMore Information about HSTS: https://aka.ms/HC-HSTS" - DisplayWriteType = "Yellow" - TestingName = 'hsts-MoreInfo' - DisplayTestingValue = $true - DisplayCustomTabNumber = 2 - } - Add-AnalyzedResultInformation @params - } - } - } elseif ($null -ne $exchangeInformation.IISSettings.ApplicationHostConfig) { - Write-Verbose "Wasn't able find any other IIS settings, likely due to application host config file being messed up." - try { - [xml]$exchangeInformation.IISSettings.ApplicationHostConfig | Out-Null - Write-Verbose "Application Host Config file is in a readable file, not sure how we got here." - } catch { - Invoke-CatchActions - Write-Verbose "Confirmed Application Host Config file isn't in a readable xml format." - $params = $baseParams + @{ - Name = "Invalid Configuration File" - Details = "Application Host Config File: '$($env:WINDIR)\System32\inetSrv\config\applicationHost.config'" - DisplayWriteType = "Red" - TestingName = "Invalid Configuration File - Application Host Config File" - DisplayTestingValue = $true - } - Add-AnalyzedResultInformation @params - } - } } diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 index 076c7ba066..75d3a6bba1 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 @@ -28,9 +28,49 @@ function Invoke-AnalyzerIISInformation { return } - ######################## - # IIS Web Sites - ######################## + if ($null -eq $exchangeInformation.IISSettings.IISWebApplication -and + $null -eq $exchangeInformation.IISSettings.IISWebSite -and + $null -eq $exchangeInformation.IISSettings.IISSharedWebConfig) { + Write-Verbose "Wasn't able find any other IIS settings, likely due to application host config file being messed up." + + if ($null -ne $exchangeInformation.IISSettings.ApplicationHostConfig) { + Write-Verbose "Wasn't able find any other IIS settings, likely due to application host config file being messed up." + try { + [xml]$exchangeInformation.IISSettings.ApplicationHostConfig | Out-Null + Write-Verbose "Application Host Config file is in a readable file, not sure how we got here." + $displayIISIssueToReport = $true + } catch { + Invoke-CatchActions + Write-Verbose "Confirmed Application Host Config file isn't in a readable xml format." + $params = $baseParams + @{ + Name = "Invalid Configuration File" + Details = "Application Host Config File: '$($env:WINDIR)\System32\inetSrv\config\applicationHost.config'" + DisplayWriteType = "Red" + TestingName = "Invalid Configuration File - Application Host Config File" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + } + } else { + Write-Verbose "No application host config file was collected either. not sure how we got here." + $displayIISIssueToReport = $true + } + + if ($displayIISIssueToReport) { + $params = $baseParams + @{ + Name = "Unknown IIS configuration" + Details = "Please report this to ExToolsFeedback@microsoft.com" + DisplayWriteType = "Red" + } + Add-AnalyzedResultInformation @params + } + # Nothing to process if we don't have the information. + return + } + + ################################### + # IIS Web Sites - Standard Display + ################################### Write-Verbose "Working on IIS Web Sites" $outputObjectDisplayValue = New-Object System.Collections.Generic.List[object] @@ -46,6 +86,8 @@ function Invoke-AnalyzerIISInformation { $webSite.Bindings.bindingInformation | ForEach-Object { if ($bindingInformationLength -lt $_.Length) { $bindingInformationLength = $_.Length } } + $hstsEnabled = $webSite.Hsts.NativeHstsSettings.enabled -eq $true -or $webSite.Hsts.HstsViaCustomHeader.enabled -eq $true + $value = @($webSite.Bindings | ForEach-Object { $certHash = $(if ($null -ne $_.certificateHash) { $_.certificateHash } else { "NULL" }) $pSpace = [string]::Empty @@ -58,6 +100,7 @@ function Invoke-AnalyzerIISInformation { $outputObjectDisplayValue.Add([PSCustomObject]@{ Name = $webSite.Name State = $webSite.State + "HSTS Enabled" = $hstsEnabled $bindingsPropertyName = $value }) } @@ -75,6 +118,124 @@ function Invoke-AnalyzerIISInformation { } Add-AnalyzedResultInformation @params + ######################## + # IIS Web Sites - Issues + ######################## + + if (($iisWebSites.Hsts.NativeHstsSettings.enabled -notcontains $true) -and + ($iisWebSites.Hsts.HstsViaCustomHeader.enabled -notcontains $true)) { + Write-Verbose "Skipping over HSTS issues, as it isn't enabled" + } else { + $showAdditionalHstsInformation = $false + + foreach ($webSite in $iisWebSites) { + $hstsConfiguration = $null + $isExchangeBackEnd = $webSite.Name -eq "Exchange Back End" + $hstsMaxAgeWriteType = "Green" + + if (($webSite.Hsts.NativeHstsSettings.enabled) -or + ($webSite.Hsts.HstsViaCustomHeader.enabled)) { + $params = $baseParams + @{ + Name = "HSTS Enabled" + Details = "$($webSite.Name)" + TestingName = "hsts-Enabled-$($webSite.Name)" + DisplayTestingValue = $true + DisplayWriteType = if ($isExchangeBackEnd) { "Red" } else { "Green" } + } + Add-AnalyzedResultInformation @params + + if ($isExchangeBackEnd) { + $showAdditionalHstsInformation = $true + $params = $baseParams + @{ + Details = "HSTS on 'Exchange Back End' is not supported and can cause issues" + DisplayWriteType = "Red" + TestingName = "hsts-BackendNotSupported" + DisplayTestingValue = $true + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + + if (($webSite.Hsts.NativeHstsSettings.enabled) -and + ($webSite.Hsts.HstsViaCustomHeader.enabled)) { + $showAdditionalHstsInformation = $true + Write-Verbose "HSTS conflict detected" + $params = $baseParams + @{ + Details = ("HSTS configured via customHeader and native IIS control - please remove one configuration" + + "`r`n`t`tHSTS native IIS control has a higher weight than the customHeader and will be used") + DisplayWriteType = "Yellow" + TestingName = "hsts-conflict" + DisplayTestingValue = $true + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + + if ($webSite.Hsts.NativeHstsSettings.enabled) { + Write-Verbose "HSTS configured via native IIS control" + $hstsConfiguration = $webSite.Hsts.NativeHstsSettings + } else { + Write-Verbose "HSTS configured via customHeader" + $hstsConfiguration = $webSite.Hsts.HstsViaCustomHeader + } + + $maxAgeValue = $hstsConfiguration.'max-age' + if ($maxAgeValue -lt 31536000) { + $showAdditionalHstsInformation = $true + $hstsMaxAgeWriteType = "Yellow" + } + $params = $baseParams + @{ + Details = "max-age: $maxAgeValue" + DisplayWriteType = $hstsMaxAgeWriteType + TestingName = "hsts-max-age-$($webSite.Name)" + DisplayTestingValue = $maxAgeValue + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + + $params = $baseParams + @{ + Details = "includeSubDomains: $($hstsConfiguration.includeSubDomains)" + TestingName = "hsts-includeSubDomains-$($webSite.Name)" + DisplayTestingValue = $hstsConfiguration.includeSubDomains + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + + $params = $baseParams + @{ + Details = "preload: $($hstsConfiguration.preload)" + TestingName = "hsts-preload-$($webSite.Name)" + DisplayTestingValue = $hstsConfiguration.preload + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + + $redirectHttpToHttpsConfigured = $hstsConfiguration.redirectHttpToHttps + $params = $baseParams + @{ + Details = "redirectHttpToHttps: $redirectHttpToHttpsConfigured" + TestingName = "hsts-redirectHttpToHttps-$($webSite.Name)" + DisplayTestingValue = $redirectHttpToHttpsConfigured + DisplayCustomTabNumber = 2 + } + if ($redirectHttpToHttpsConfigured) { + $showAdditionalHstsInformation = $true + $params.Add("DisplayWriteType", "Red") + } + Add-AnalyzedResultInformation @params + } + } + + if ($showAdditionalHstsInformation) { + $params = $baseParams + @{ + Details = "`r`n`t`tMore Information about HSTS: https://aka.ms/HC-HSTS" + DisplayWriteType = "Yellow" + TestingName = 'hsts-MoreInfo' + DisplayTestingValue = $true + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + } + ######################## # IIS Web App Pools ######################## @@ -172,6 +333,188 @@ function Invoke-AnalyzerIISInformation { Add-AnalyzedResultInformation @params } + ######################## + # Virtual Directories + ######################## + + $iisWebSettings = @($exchangeInformation.IISSettings.IISWebApplication) + $iisWebSettings += @($exchangeInformation.IISSettings.IISWebSite) + $iisConfigurationSettings = @($exchangeInformation.IISSettings.IISWebApplication.ConfigurationFileInfo) + $iisConfigurationSettings += $iisWebSiteConfigs = @($exchangeInformation.IISSettings.IISWebSite.ConfigurationFileInfo) + $iisConfigurationSettings += @($exchangeInformation.IISSettings.IISSharedWebConfig) + + # Invalid configuration files are ones that we can't convert to xml. + $invalidConfigurationFile = $iisConfigurationSettings | Where-Object { $_.Valid -eq $false -and $_.Exist -eq $true } + # If a web application config file doesn't truly exists, we end up using the parent web.config file + # If any of the web application config file paths match a parent path, that is a problem. + # only collect the ones that are valid, if not valid we will assume that the child web apps will point to it and can be misleading. + $siteConfigPaths = $iisWebSiteConfigs | + Where-Object { $_.Valid -eq $true -and $_.Exist -eq $true } | + ForEach-Object { $_.Location.ToLower() } + + if ($null -ne $siteConfigPaths) { + $missingWebApplicationConfigFile = $exchangeInformation.IISSettings.IISWebApplication | + Where-Object { $siteConfigPaths.Contains($_.ConfigurationFileInfo.Location.ToLower()) } + } + + # Missing config file should really only occur for SharedWebConfig files, as the web application would go back to the parent site. + $missingSharedConfigFile = @($exchangeInformation.IISSettings.IISSharedWebConfig) | Where-Object { $_.Exist -eq $false } + $missingConfigFiles = $iisWebSettings | Where-Object { $_.ConfigurationFileInfo.Exist -eq $false } + $defaultVariableDetected = $iisConfigurationSettings | Where-Object { $null -ne ($_.Content | Select-String "%ExchangeInstallDir%") } + $binSearchFoldersNotFound = $iisConfigurationSettings | + Where-Object { $_.Location -like "*\ClientAccess\ecp\web.config" -and $_.Exist -eq $true -and $_.Valid -eq $true } | + Where-Object { + $binSearchFolders = (([xml]($_.Content)).configuration.appSettings.add | Where-Object { + $_.key -eq "BinSearchFolders" + }).value + $paths = $binSearchFolders.Split(";").Trim().ToLower() + $paths | ForEach-Object { Write-Verbose "BinSearchFolder: $($_)" } + $installPath = $exchangeInformation.RegistryValues.MsiInstallPath + foreach ($binTestPath in @("bin", "bin\CmdletExtensionAgents", "ClientAccess\Owa\bin")) { + $testPath = [System.IO.Path]::Combine($installPath, $binTestPath).ToLower() + Write-Verbose "Testing path: $testPath" + if (-not ($paths.Contains($testPath))) { + return $_ + } + } + } + + if ($null -ne $missingWebApplicationConfigFile) { + $params = $baseParams + @{ + Name = "Missing Web Application Configuration File" + DisplayWriteType = "Red" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + + foreach ($webApp in $missingWebApplicationConfigFile) { + $params = $baseParams + @{ + Details = "Web Application: '$($webApp.FriendlyName)' Attempting to use config: '$($webApp.ConfigurationFileInfo.Location)'" + DisplayWriteType = "Red" + DisplayCustomTabNumber = 2 + TestingName = "Web Application: '$($webApp.FriendlyName)'" + DisplayTestingValue = $($webApp.ConfigurationFileInfo.Location) + } + Add-AnalyzedResultInformation @params + } + } + + if ($null -ne $invalidConfigurationFile) { + $params = $baseParams + @{ + Name = "Invalid Configuration File" + DisplayWriteType = "Red" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + + $alreadyDisplayConfigs = New-Object 'System.Collections.Generic.HashSet[string]' + foreach ($configFile in $invalidConfigurationFile) { + if ($alreadyDisplayConfigs.Add($configFile.Location)) { + $params = $baseParams + @{ + Details = "Invalid: $($configFile.Location)" + DisplayWriteType = "Red" + DisplayCustomTabNumber = 2 + TestingName = "Invalid: $($configFile.Location)" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + } + } + } + + if ($null -ne $missingSharedConfigFile) { + $params = $baseParams + @{ + Name = "Missing Shared Configuration File" + DisplayWriteType = "Red" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + + foreach ($file in $missingSharedConfigFile) { + $params = $baseParams + @{ + Details = "Missing: $($file.Location)" + DisplayWriteType = "Red" + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + + $params = $baseParams + @{ + Details = "More Information: https://aka.ms/HC-MissingConfig" + DisplayWriteType = "Yellow" + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + + if ($null -ne $missingConfigFiles) { + $params = $baseParams + @{ + Name = "Couldn't Find Config File" + DisplayWriteType = "Red" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + + foreach ($file in $missingConfigFiles) { + $params = $baseParams + @{ + Details = "Friendly Name: $($file.FriendlyName)" + DisplayWriteType = "Red" + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + } + + if ($null -ne $defaultVariableDetected) { + $params = $baseParams + @{ + Name = "Default Variable Detected" + DisplayWriteType = "Red" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + + foreach ($file in $defaultVariableDetected) { + $params = $baseParams + @{ + Details = "$($file.Location)" + DisplayWriteType = "Red" + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + + $params = $baseParams + @{ + Details = "More Information: https://aka.ms/HC-DefaultVariableDetected" + DisplayWriteType = "Yellow" + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + + if ($null -ne $binSearchFoldersNotFound) { + $params = $baseParams + @{ + Name = "Bin Search Folder Not Found" + DisplayWriteType = "Red" + DisplayTestingValue = $true + } + Add-AnalyzedResultInformation @params + + foreach ($file in $binSearchFoldersNotFound) { + $params = $baseParams + @{ + Details = "$($file.Location)" + DisplayWriteType = "Red" + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + + $params = $baseParams + @{ + Details = "More Information: https://aka.ms/HC-BinSearchFolder" + DisplayWriteType = "Yellow" + DisplayCustomTabNumber = 2 + } + Add-AnalyzedResultInformation @params + } + ######################## # IIS Module Information ######################## diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 index 012b644bf4..e2061193c6 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E15.Main.Tests.ps1 @@ -117,9 +117,8 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2013" { TestObjectMatch "Credential Guard Enabled" $false TestObjectMatch "EdgeTransport.exe.config Present" "True" -WriteType "Green" TestObjectMatch "Open Relay Wild Card Domain" "Not Set" - TestObjectMatch "HSTS Enabled" "False" - $Script:ActiveGrouping.Count | Should -Be 10 + $Script:ActiveGrouping.Count | Should -Be 9 } It "Display Results - Security Settings" { diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 index 908a83a2a5..19832342fd 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1 @@ -117,9 +117,8 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2016" { TestObjectMatch "Credential Guard Enabled" $false TestObjectMatch "EdgeTransport.exe.config Present" "True" -WriteType "Green" TestObjectMatch "Open Relay Wild Card Domain" "Not Set" - TestObjectMatch "HSTS Enabled" "False" - $Script:ActiveGrouping.Count | Should -Be 10 + $Script:ActiveGrouping.Count | Should -Be 9 } It "Display Results - Security Settings" { diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 index dbd834f5de..6293731ec1 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1 @@ -118,9 +118,8 @@ Describe "Testing Health Checker by Mock Data Imports" { TestObjectMatch "Credential Guard Enabled" $false TestObjectMatch "EdgeTransport.exe.config Present" "True" -WriteType "Green" TestObjectMatch "Open Relay Wild Card Domain" "Not Set" - TestObjectMatch "HSTS Enabled" "False" - $Script:ActiveGrouping.Count | Should -Be 10 + $Script:ActiveGrouping.Count | Should -Be 9 } It "Display Results - Security Settings" { @@ -225,17 +224,6 @@ Describe "Testing Health Checker by Mock Data Imports" { $Script:ActiveGrouping.Count | Should -Be 18 } - It "Display Results - Frequent Configuration Issues" { - SetActiveDisplayGrouping "Frequent Configuration Issues" - TestObjectMatch "hsts-Enabled-Default Web Site" $true -WriteType "Green" - TestObjectMatch "hsts-max-age-Default Web Site" 300 -WriteType "Yellow" - TestObjectMatch "hsts-includeSubDomains-Default Web Site" $false - TestObjectMatch "hsts-preload-Default Web Site" $false - TestObjectMatch "hsts-redirectHttpToHttps-Default Web Site" $false - TestObjectMatch "hsts-conflict" $true -WriteType "Yellow" - TestObjectMatch "hsts-MoreInfo" $true -WriteType "Yellow" - } - It "Display Results - Security Settings" { SetActiveDisplayGrouping "Security Settings" TestObjectMatch "AMSI Enabled" "True" -WriteType "Green" @@ -258,6 +246,14 @@ Describe "Testing Health Checker by Mock Data Imports" { SetActiveDisplayGrouping "Exchange IIS Information" $tokenCacheModuleInformation = GetObject "TokenCacheModule loaded" $tokenCacheModuleInformation | Should -Be $null # null because we are loaded + + TestObjectMatch "hsts-Enabled-Default Web Site" $true -WriteType "Green" + TestObjectMatch "hsts-max-age-Default Web Site" 300 -WriteType "Yellow" + TestObjectMatch "hsts-includeSubDomains-Default Web Site" $false + TestObjectMatch "hsts-preload-Default Web Site" $false + TestObjectMatch "hsts-redirectHttpToHttps-Default Web Site" $false + TestObjectMatch "hsts-conflict" $true -WriteType "Yellow" + TestObjectMatch "hsts-MoreInfo" $true -WriteType "Yellow" } } diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 index 3b6d070d9d..7ec36702db 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios.Tests.ps1 @@ -80,8 +80,6 @@ Describe "Testing Health Checker by Mock Data Imports" { It "TCP Keep Alive Time" { SetActiveDisplayGrouping "Frequent Configuration Issues" TestObjectMatch "TCP/IP Settings" 0 -WriteType "Red" - TestObjectMatch "Missing Web Application Configuration File" $true -WriteType "Red" - TestObjectMatch "Web Application: 'Default Web Site/ecp'" "$Script:MockDataCollectionRoot\Exchange\IIS\DefaultWebSite_web.config" -WriteType "Red" } It "CTS Processor Affinity Percentage" { @@ -100,39 +98,6 @@ Describe "Testing Health Checker by Mock Data Imports" { TestObjectMatch "Open Relay Wild Card Domain" "Error --- Accepted Domain `"Problem Accepted Domain`" is set to a Wild Card (*) Domain Name with a domain type of InternalRelay. This is not recommended as this is an open relay for the entire environment.`r`n`t`tMore Information: https://aka.ms/HC-OpenRelayDomain" -WriteType "Red" } - It "Testing Missing Shared Configuration File" { - TestObjectMatch "Missing Shared Configuration File" $true -WriteType "Red" - } - - It "Testing Default Variable Detected" { - TestObjectMatch "Default Variable Detected" $true -WriteType "Red" - } - - It "Testing Bin Search Folder Not Found" { - TestObjectMatch "Bin Search Folder Not Found" $true -WriteType "Red" - } - - It "Testing Native HSTS Default Web Site config" { - #Native Default Web Site - TestObjectMatch "hsts-Enabled-Default Web Site" $true -WriteType "Green" - TestObjectMatch "hsts-max-age-Default Web Site" 300 -WriteType "Yellow" - TestObjectMatch "hsts-includeSubDomains-Default Web Site" $false - TestObjectMatch "hsts-preload-Default Web Site" $false - TestObjectMatch "hsts-redirectHttpToHttps-Default Web Site" $false - } - - It "Testing Native HSTS Default Web Site config" { - #Native Exchange Back End - TestObjectMatch "hsts-Enabled-Exchange Back End" $true -WriteType "Red" - TestObjectMatch "hsts-max-age-Exchange Back End" 31536000 -WriteType "Green" # Going to be green even on backend - TestObjectMatch "hsts-includeSubDomains-Exchange Back End" $false - TestObjectMatch "hsts-preload-Exchange Back End" $false - TestObjectMatch "hsts-redirectHttpToHttps-Exchange Back End" $true -WriteType "Red" - TestObjectMatch "hsts-BackendNotSupported" $true -WriteType "Red" - - TestObjectMatch "hsts-MoreInfo" $true -WriteType "Yellow" - } - It "Server Pending Reboot" { SetActiveDisplayGrouping "Operating System Information" TestObjectMatch "Server Pending Reboot" "True" -WriteType "Yellow" @@ -195,6 +160,45 @@ Describe "Testing Health Checker by Mock Data Imports" { SetActiveDisplayGrouping "Exchange IIS Information" TestObjectMatch "TokenCacheModule loaded" $true -WriteType "Yellow" } + + It "Missing Web Application Configuration File" { + SetActiveDisplayGrouping "Exchange IIS Information" + TestObjectMatch "Missing Web Application Configuration File" $true -WriteType "Red" + TestObjectMatch "Web Application: 'Default Web Site/ecp'" "$Script:MockDataCollectionRoot\Exchange\IIS\DefaultWebSite_web.config" -WriteType "Red" + } + + It "Testing Missing Shared Configuration File" { + TestObjectMatch "Missing Shared Configuration File" $true -WriteType "Red" + } + + It "Testing Default Variable Detected" { + TestObjectMatch "Default Variable Detected" $true -WriteType "Red" + } + + It "Testing Bin Search Folder Not Found" { + TestObjectMatch "Bin Search Folder Not Found" $true -WriteType "Red" + } + + It "Testing Native HSTS Default Web Site config" { + #Native Default Web Site + TestObjectMatch "hsts-Enabled-Default Web Site" $true -WriteType "Green" + TestObjectMatch "hsts-max-age-Default Web Site" 300 -WriteType "Yellow" + TestObjectMatch "hsts-includeSubDomains-Default Web Site" $false + TestObjectMatch "hsts-preload-Default Web Site" $false + TestObjectMatch "hsts-redirectHttpToHttps-Default Web Site" $false + } + + It "Testing Native HSTS Default Web Site config" { + #Native Exchange Back End + TestObjectMatch "hsts-Enabled-Exchange Back End" $true -WriteType "Red" + TestObjectMatch "hsts-max-age-Exchange Back End" 31536000 -WriteType "Green" # Going to be green even on backend + TestObjectMatch "hsts-includeSubDomains-Exchange Back End" $false + TestObjectMatch "hsts-preload-Exchange Back End" $false + TestObjectMatch "hsts-redirectHttpToHttps-Exchange Back End" $true -WriteType "Red" + TestObjectMatch "hsts-BackendNotSupported" $true -WriteType "Red" + + TestObjectMatch "hsts-MoreInfo" $true -WriteType "Yellow" + } } Context "Checking Scenarios 2" { diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios2.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios2.Tests.ps1 index 1c53d33f47..56575d7b03 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios2.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.E19.Scenarios2.Tests.ps1 @@ -41,7 +41,7 @@ Describe "Exchange 2019 Scenarios testing 2" { } It "Bad application host config file" { - SetActiveDisplayGrouping "Frequent Configuration Issues" + SetActiveDisplayGrouping "Exchange IIS Information" TestObjectMatch "Invalid Configuration File - Application Host Config File" $true -WriteType "Red" $m = GetObject "Missing Web Application Configuration File" $m | Should -Be $null @@ -76,7 +76,7 @@ Describe "Exchange 2019 Scenarios testing 2" { } It "Bad Default Web Site web.config file" { - SetActiveDisplayGrouping "Frequent Configuration Issues" + SetActiveDisplayGrouping "Exchange IIS Information" TestObjectMatch "Invalid Configuration File" $true -WriteType "Red" TestObjectMatch "Invalid: $Script:MockDataCollectionRoot\Exchange\IIS\DefaultWebSite_web1.config" $true -WriteType "Red" TestObjectMatch "Missing Web Application Configuration File" $true -WriteType "Red" From a448543348225f090ccc2e91c922d318e6555dba Mon Sep 17 00:00:00 2001 From: David Paulson Date: Sat, 18 Nov 2023 12:14:58 -0600 Subject: [PATCH 05/15] New IIS Display Section --- .../Analyzer/Get-IISAuthenticationType.ps1 | 216 ++++++++++++++++++ .../Analyzer/Get-IPFilterSetting.ps1 | 71 ++++++ .../Analyzer/Get-URLRewriteRule.ps1 | 113 +++++++++ .../Invoke-AnalyzerIISInformation.ps1 | 150 +++++++++++- 4 files changed, 547 insertions(+), 3 deletions(-) create mode 100644 Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 create mode 100644 Diagnostics/HealthChecker/Analyzer/Get-IPFilterSetting.ps1 create mode 100644 Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 diff --git a/Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 b/Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 new file mode 100644 index 0000000000..db9ddf6785 --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 @@ -0,0 +1,216 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function Get-IISAuthenticationType { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [System.Xml.XmlNode]$ApplicationHostConfig + ) + begin { + + function GetAuthTypeName { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$AuthType, + + [object]$CurrentAuthLocation, + + [Parameter(Mandatory = $true)] + [string]$MainLocation, + + [Parameter(Mandatory = $true)] + [ref]$Completed + ) + begin { + $Completed.Value = $false + $CurrentAuthLocation = $CurrentAuthLocation.$AuthType + $returnValue = [string]::Empty + } + process { + if ($null -ne $CurrentAuthLocation -and + $null -ne $CurrentAuthLocation.enabled) { + # Found setting here, set to completed + $Completed.Value = $true + + if ($CurrentAuthLocation.enabled -eq "false") { + Write-Verbose "Disabled auth type." + return + } + + # evaluate auth types to add to list of enabled. + if ($AuthType -eq "anonymousAuthentication") { + # provided 'anonymous (default setting)' for the locations that are expected. + # API, Autodiscover, ecp, ews, OWA (BE), Default Web Site, Exchange Back End + # use MainLocation because that is the location we are evaluating + if ($MainLocation -like "*/API" -or + $MainLocation -like "*/Autodiscover" -or + $MainLocation -like "*/ecp" -or + $MainLocation -like "*/EWS" -or + $MainLocation -eq "Exchange Back End/OWA" -or + $MainLocation -eq "Default Web Site" -or + $MainLocation -eq "Exchange Back End") { + $returnValue = "anonymous (default setting)" + } else { + $returnValue = "anonymous (NOT default setting)" + } + } elseif ($AuthType -eq "windowsAuthentication") { + # If clear is set, we only use the value here + # If clear is set, we add to the default location of provider types. + + if ($null -ne $CurrentAuthLocation.providers.clear -or + $null -eq $defaultWindowsAuthProviders -or + $defaultWindowsAuthProviders.Count -eq 0) { + + if ($null -ne $CurrentAuthLocation.providers.add.value) { + $returnValue = "Windows ($($CurrentAuthLocation.providers.add.value -join ","))" + } else { + $returnValue = "Windows (No providers)" # This could be a problem?? + } + } else { + $localAuthProviders = @($defaultWindowsAuthProviders) + + if ($null -ne $CurrentAuthLocation.providers.add.value) { + $localAuthProviders += $CurrentAuthLocation.providers.add.value + } + + $returnValue = "Windows ($($localAuthProviders -join ","))" + } + } else { + $returnValue = $AuthType.Replace("Authentication", "").Replace("ClientCertificateMapping", "Cert") + } + } else { + # If not set here, we need to look at the parent + Write-Verbose "Not set at current location. Need to look at parent." + } + } end { + if (-not ([string]::IsNullOrEmpty($returnValue))) { Write-Verbose "Value Set: $returnValue" } + + return $returnValue + } + } + + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $getIisAuthenticationType = @{} + $appHostConfigLocations = $ApplicationHostConfig.configuration.Location.path | Where-Object { $_ -ne "" } + $defaultWindowsAuthProviders = @($ApplicationHostConfig.configuration.'system.webServer'.security.authentication.windowsAuthentication.providers.add.value) + $authenticationTypes = @("windowsAuthentication", "anonymousAuthentication", "digestAuthentication", "basicAuthentication", + "clientCertificateMappingAuthentication", "iisClientCertificateMappingAuthentication") + $failedKey = "FailedLocations" + $getIisAuthenticationType.Add($failedKey, (New-Object System.Collections.Generic.List[object])) + } + process { + # foreach each location, we need to look for each $authenticationTypes up the stack ordering to determine if it is enabled or not. + # for this configuration type, clear flag doesn't appear to be used at all. + foreach ($appKey in $appHostConfigLocations) { + Write-Verbose "Working on appKey: $appKey" + $getIisAuthenticationType.Add($appKey, [string]::Empty) + $currentKey = $appKey + $authentication = @() + $continue = $true + $authenticationTypeCompleted = @{} + $authenticationTypes | ForEach-Object { $authenticationTypeCompleted.Add($_, $false) } + + do { + # to avoid doing a lot of loops, evaluate each location for all the authentication types before moving up a level. + Write-Verbose "Working on currentKey: $currentKey" + $location = $ApplicationHostConfig.SelectNodes("/configuration/location[@path = '$currentKey']") + + if ($null -ne $location -and + $null -ne $location.path) { + $authLocation = $location.'system.webServer'.security.authentication + + if ($null -ne $authLocation) { + # look over each auth type + foreach ($authenticationType in $authenticationTypes) { + if ($authenticationTypeCompleted[$authenticationType]) { + # we already have this auth type evaluated don't use this setting here. + continue + } + + Write-Verbose "Evaluating current authenticationType: $authenticationType" + $didComplete = $false + $params = @{ + AuthType = $authenticationType + CurrentAuthLocation = $authLocation + MainLocation = $appKey + Completed = [ref]$didComplete + } + + $value = GetAuthTypeName @params + if ($didComplete) { + $authenticationTypeCompleted[$authenticationType] = $true + + if (-not ([string]::IsNullOrEmpty($value))) { + $authentication += $value + } + } + } + $continue = $null -ne ($authenticationTypeCompleted.Values | Where-Object { $_ -eq $false }) + + if ($continue) { + $index = $currentKey.LastIndexOf("/") + + if ($index -eq -1) { + $continue = $false + $defaultAuthLocation = $ApplicationHostConfig.configuration.'system.webServer'.security.authentication + + foreach ($authenticationType in $authenticationTypes) { + if ($authenticationTypeCompleted[$authenticationType]) { + # we already have this auth type evaluated don't use this setting here. + continue + } + + Write-Verbose "Evaluating global current authenticationType: $authenticationType" + $didComplete = $false + $params = @{ + AuthType = $authenticationType + CurrentAuthLocation = $defaultAuthLocation + MainLocation = $appKey + Completed = [ref]$didComplete + } + + $value = GetAuthTypeName @params + if ($didComplete) { + $authenticationTypeCompleted[$authenticationType] = $true + + if (-not ([string]::IsNullOrEmpty($value))) { + $authentication += $value + } + } + } + } else { + $currentKey = $currentKey.Substring(0, $index) + } + } + } else { + Write-Verbose "authLocation was NULL, but shouldn't be a problem we just use the parent." + } + } elseif ($currentKey -ne $appKey) { + # If we are at a parent location we might not have all the locations in the config. So this could be okay. + Write-Verbose "Couldn't find location for '$currentKey'. Keep on looking" + $index = $currentKey.LastIndexOf("/") + + if ($index -eq -1) { + Write-Verbose "Didn't have root parent in the config file, this is odd." + $getIisAuthenticationType[$failedKey].Add($appKey) + } else { + $currentKey = $currentKey.Substring(0, $index) + } + } else { + Write-Verbose "Couldn't find location. This shouldn't occur." + # Add to failed key to display issue + $getIisAuthenticationType[$failedKey].Add($appKey) + } + } while ($continue) + + $getIisAuthenticationType[$appKey] = $authentication + Write-Verbose "Found auth types for enabled for '$appKey': $($authentication -join ",")" + } + } + end { + return $getIisAuthenticationType + } +} diff --git a/Diagnostics/HealthChecker/Analyzer/Get-IPFilterSetting.ps1 b/Diagnostics/HealthChecker/Analyzer/Get-IPFilterSetting.ps1 new file mode 100644 index 0000000000..ce7564eb62 --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Get-IPFilterSetting.ps1 @@ -0,0 +1,71 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function Get-IPFilterSetting { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [System.Xml.XmlNode]$ApplicationHostConfig + ) + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $locationPaths = $ApplicationHostConfig.configuration.location.path | Where-Object { $_ -ne "" } + $ipFilterSettings = @{} + } + process { + foreach ($appKey in $locationPaths) { + Write-Verbose "Working on appKey: $appKey" + $ipFilterSettings.Add($appKey, (New-Object System.Collections.Generic.List[object])) + $currentKey = $appKey + $continue = $true + + do { + Write-Verbose "Working on currentKey: $currentKey" + $location = $ApplicationHostConfig.SelectNodes("/configuration/location[@path = '$currentKey']") + + if ($null -ne $location) { + $ipSecurity = $location.'system.webServer'.security.ipSecurity + + if ($null -ne $ipSecurity) { + $clear = $null -ne $ipSecurity.clear + $ipFilterSettings[$appKey].Add($ipSecurity) + } + } else { + Write-Verbose "Couldn't find location. This shouldn't occur." + } + + if ($clear) { + Write-Verbose "Clear was set, don't need to know what else was set." + $continue = $false + } else { + $index = $currentKey.LastIndexOf("/") + + if ($index -eq -1) { + $continue = $false + + # look at the global configuration applicationHost.config + $ipSecurity = $ApplicationHostConfig.configuration.'system.webServer'.security.ipSecurity + + # Need to check for if it is an empty string, if it is, we don't need to worry about it. + if ($null -ne $ipSecurity -and + $ipSecurity.GetType().Name -ne "string") { + $add = $null -ne ($ipSecurity | Get-Member | Where-Object { $_.MemberType -eq "Property" -and $_.Name -ne "allowUnlisted" }) + + if ($add) { + $ipFilterSettings[$appKey].Add($ipSecurity) + } + } else { + Write-Verbose "No ipSecurity set globally" + } + } else { + $currentKey = $currentKey.Substring(0, $index) + } + } + } while ($continue) + } + } + end { + return $ipFilterSettings + } +} diff --git a/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 b/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 new file mode 100644 index 0000000000..cd697be19a --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 @@ -0,0 +1,113 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# + This is a function that is designed to pull out the URL Rewrite Rules that are set on a location of IIS. + Because you can set it on an individual web.config file or the parent site(s), or the ApplicationHostConfig file for the location + We need to check all locations to properly determine what is all set. + The ApplicationHostConfig file must be able to be converted to Xml, but the web.config file doesn't. + The order goes like this it appears based off testing done, if overrides are allowed which by default for URL Rewrite that is true. + 1. Current IIS Location for web.config for virtual directory + 2. ApplicationHost.config file for the same location + 3. Then move up one level (Default Web Site/mapi -> Default Web Site) and repeat 1 and 2 till no more locations. + a. If the 'clear' flag was set at any point, we stop at that location in the process. + 4. Then there is a global setting in the ApplicationHost.config file. +#> +function Get-URLRewriteRule { + [CmdletBinding()] + [OutputType([hashtable])] + param( + [Parameter(Mandatory = $true)] + [System.Xml.XmlNode]$ApplicationHostConfig, + + # Key = IIS Location (Example: Default Web Site/mapi) + # Value = web.config content + [Parameter(Mandatory = $true)] + [hashtable]$WebConfigContent + ) + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $urlRewriteRules = @{} + $appHostConfigLocations = $ApplicationHostConfig.configuration.Location.path + } + process { + foreach ($key in $WebConfigContent.Keys) { + Write-Verbose "Working on key: $key" + $continue = $true + $clear = $false + $currentKey = $key + $urlRewriteRules.Add($key, (New-Object System.Collections.Generic.List[object])) + + do { + Write-Verbose "Working on currentKey: $currentKey" + try { + # the Web.config is looked at first + [xml]$content = $WebConfigContent[$currentKey] + $rules = $content.configuration.'system.webServer'.rewrite.rules + + if ($null -ne $rules) { + $clear = $null -ne $rules.clear + $urlRewriteRules[$key].Add($rules) + } else { + Write-Verbose "No rewrite rules in the config file" + } + } catch { + Write-Verbose "Failed to convert to xml" + Invoke-CatchActions + } + + if (-not $clear) { + # Now need to look at the applicationHost.config file to determine what is set at that location. + # need to do this because of the case sensitive query to get the xmlNode + Write-Verbose "clear not set on config. Looking at the applicationHost.config file" + $appKey = $appHostConfigLocations | Where-Object { $_ -eq $currentKey } + + if ($appKey.Count -eq 1) { + $location = $ApplicationHostConfig.SelectNodes("/configuration/location[@path = '$appKey']") + + if ($null -ne $location) { + $rules = $location.'system.webServer'.rewrite.rules + + if ($null -ne $rules) { + $clear = $null -ne $rules.clear + $urlRewriteRules[$key].Add($rules) + } else { + Write-Verbose 'No rewrite rules in the applicationHost.config file' + } + } else { + Write-Verbose "We didn't find the location for '$appKey' in the applicationHostConfig. This shouldn't occur." + } + } else { + Write-Verbose "Multiple appKeys locations found for currentKey" + } + } + + if ($clear) { + Write-Verbose "Clear was set, don't need to know what else was set." + $continue = $false + } else { + $index = $currentKey.LastIndexOf("/") + + if ($index -eq -1) { + $continue = $false + # look at the global configuration of the applicationHost.config file + $rules = $ApplicationHostConfig.configuration.'system.webServer'.rewrite.rules + + if ($null -ne $rules) { + $urlRewriteRules[$key].Add($rules) + } else { + Write-Verbose "No global configuration for rewrite rules." + } + } else { + $currentKey = $currentKey.Substring(0, $index) + } + } + } while ($continue) + + Write-Verbose "Completed key: $key" + } + } + end { + return $urlRewriteRules + } +} diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 index 75d3a6bba1..ccfaefe80c 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 @@ -3,6 +3,9 @@ . $PSScriptRoot\Add-AnalyzedResultInformation.ps1 . $PSScriptRoot\Get-DisplayResultsGroupingKey.ps1 +. $PSScriptRoot\Get-IISAuthenticationType.ps1 +. $PSScriptRoot\Get-IPFilterSetting.ps1 +. $PSScriptRoot\Get-URLRewriteRule.ps1 function Invoke-AnalyzerIISInformation { [CmdletBinding()] param( @@ -333,15 +336,106 @@ function Invoke-AnalyzerIISInformation { Add-AnalyzedResultInformation @params } - ######################## - # Virtual Directories - ######################## + ######################################## + # Virtual Directories - Standard display + ######################################## + $applicationHostConfig = $exchangeInformation.IISSettings.ApplicationHostConfig $iisWebSettings = @($exchangeInformation.IISSettings.IISWebApplication) $iisWebSettings += @($exchangeInformation.IISSettings.IISWebSite) $iisConfigurationSettings = @($exchangeInformation.IISSettings.IISWebApplication.ConfigurationFileInfo) $iisConfigurationSettings += $iisWebSiteConfigs = @($exchangeInformation.IISSettings.IISWebSite.ConfigurationFileInfo) $iisConfigurationSettings += @($exchangeInformation.IISSettings.IISSharedWebConfig) + $extendedProtectionConfiguration = $exchangeInformation.ExtendedProtectionConfig.ExtendedProtectionConfiguration + $displayMainSitesList = @("Default Web Site", "API", "Autodiscover", "ecp", "EWS", "mapi", "Microsoft-Server-ActiveSync", "OAB", "owa", + "PowerShell", "Rpc", "Exchange Back End", "emsmdb", "nspi", "RpcWithCert") + $iisVirtualDirectoriesDisplay = New-Object 'System.Collections.Generic.List[System.Object]' + $iisWebConfigContent = @{} + $iisLocations = ([xml]$applicationHostConfig).configuration.Location | Sort-Object Path + + $iisWebSettings | ForEach-Object { + $key = if ($null -ne $_.FriendlyName) { $_.FriendlyName } else { $_.Name } + $iisWebConfigContent.Add($key, $_.ConfigurationFileInfo.Content) + } + + $ruleParams = @{ + ApplicationHostConfig = [xml]$applicationHostConfig + WebConfigContent = $iisWebConfigContent + } + + $urlRewriteRules = Get-URLRewriteRule @ruleParams + $ipFilterSettings = Get-IPFilterSetting -ApplicationHostConfig ([xml]$applicationHostConfig) + $authTypeSettings = Get-IISAuthenticationType -ApplicationHostConfig ([xml]$applicationHostConfig) + $failedLocationsForAuth = @() + Write-Verbose "Evaluating the IIS Locations for display" + + foreach ($location in $iisLocations) { + + if ([string]::IsNullOrEmpty($location.Path)) { continue } + + if ($displayMainSitesList -notcontains ($location.Path.Split("/")[-1])) { continue } + + Write-Verbose "Working on IIS Path: $($location.Path)" + $sslFlag = [string]::Empty + $displayRewriteRules = [string]::Empty + $ipFilterEnabled = $ipFilterSettings[$location.Path].Count -ne 0 + $epValue = "None" + $ep = $extendedProtectionConfiguration | Where-Object { $_.VirtualDirectoryName -eq $location.Path } + $currentRewriteRules = $urlRewriteRules[$location.Path] + $authentication = $authTypeSettings[$location.Path] + + if ($currentRewriteRules.Count -ne 0) { + $displayRewriteRules = ($currentRewriteRules.rule | Where-Object { $_.enabled -ne "false" }).name + } + + if ($null -ne $ep) { + Write-Verbose "Using EP settings to determine sslFlags" + $sslSettings = $ep.Configuration.SslSettings + $sslFlag = "$($sslSettings.RequireSSL) $(if($sslSettings.Ssl128Bit) { "(128-bit)" })".Trim() + + if ($sslSettings.ClientCertificate -ne "Ignore") { + $sslFlag = @($sslFlag, "Cert($($sslSettings.ClientCertificate))") + } + + $epValue = $ep.ExtendedProtection + } else { + Write-Verbose "Not using EP settings to determine sslFlags, skipping over cert auth logic." + $ssl = $location.'system.webServer'.security.access.SslFlags + $sslFlag = "$($ssl.ToLower().Contains("ssl")) $(if(($ssl.ToLower().Contains("ssl128"))) { "(128-bit)" })".Trim() + } + + $iisVirtualDirectoriesDisplay.Add([PSCustomObject]@{ + Name = $location.Path + ExtendedProtection = $epValue + SslFlags = $sslFlag + IPFilteringEnabled = $ipFilterEnabled + URLRewrite = $displayRewriteRules + Authentication = $authentication + }) + } + + $params = $baseParams + @{ + OutColumns = ([PSCustomObject]@{ + DisplayObject = $iisVirtualDirectoriesDisplay + IndentSpaces = 8 + }) + AddHtmlDetailRow = $false + } + Add-AnalyzedResultInformation @params + + if ($failedLocationsForAuth.Count -gt 0) { + $params = $baseParams + @{ + Name = "Inaccurate display of authentication types" + Details = $failedLocationsForAuth -join "," + DisplayWriteType = "Yellow" + } + + Add-AnalyzedResultInformation @params + } + + ############################### + # Virtual Directories - Issues + ############################### # Invalid configuration files are ones that we can't convert to xml. $invalidConfigurationFile = $iisConfigurationSettings | Where-Object { $_.Valid -eq $false -and $_.Exist -eq $true } @@ -379,6 +473,56 @@ function Invoke-AnalyzerIISInformation { } } + # Display URL Rewrite Rules. + # To save on space, don't display rules that are on multiple vDirs by same name. + # Use 'DisplayKey' for the display results. + $alreadyDisplayedUrlRewriteRules = @{} + $alreadyDisplayedUrlKey = "DisplayKey" + $alreadyDisplayedUrlRewriteRules.Add($alreadyDisplayedUrlKey, (New-Object System.Collections.Generic.List[object])) + + foreach ($key in $urlRewriteRules.Keys) { + $currentSection = $urlRewriteRules[$key] + + if ($currentSection.Count -ne 0) { + foreach ($rule in $currentSection.rule) { + + # skip over disabled rules. + if ($rule.enabled -eq "false") { + Write-Verbose "skipping over disabled rule: $($rule.Name) for vDir '$key'" + continue + } + + #multiple match type possibilities, but should only be one per rule. + $propertyType = ($rule.match | Get-Member | Where-Object { $_.MemberType -eq "Property" }).Name + $matchProperty = "$propertyType - $($rule.match.$propertyType)" + + $displayObject = [PSCustomObject]@{ + RewriteRuleName = $rule.name + Pattern = $rule.conditions.add.pattern + MatchProperty = $matchProperty + ActionType = $rule.action.type + } + + #.ContainsValue() and .ContainsKey() doesn't find the complex object it seems. Need to find it by a key and a simple name. + if (-not ($alreadyDisplayedUrlRewriteRules.ContainsKey((($displayObject.RewriteRuleName))))) { + $alreadyDisplayedUrlRewriteRules.Add($displayObject.RewriteRuleName, $displayObject) + $alreadyDisplayedUrlRewriteRules[$alreadyDisplayedUrlKey].Add($displayObject) + } + } + } + } + + if ($alreadyDisplayedUrlRewriteRules[$alreadyDisplayedUrlKey].Count -gt 0) { + $params = $baseParams + @{ + OutColumns = ([PSCustomObject]@{ + DisplayObject = $alreadyDisplayedUrlRewriteRules[$alreadyDisplayedUrlKey] + IndentSpaces = 8 + }) + AddHtmlDetailRow = $false + } + Add-AnalyzedResultInformation @params + } + if ($null -ne $missingWebApplicationConfigFile) { $params = $baseParams + @{ Name = "Missing Web Application Configuration File" From 5c20b7b7d8b1d958daa334210c8a731aeb2ecbba Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 18 Dec 2023 09:36:29 -0600 Subject: [PATCH 06/15] Avoid using ToLower() in recent code changes --- .../Analyzer/Invoke-AnalyzerIISInformation.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 index ccfaefe80c..aec0ec4c2c 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 @@ -401,7 +401,7 @@ function Invoke-AnalyzerIISInformation { } else { Write-Verbose "Not using EP settings to determine sslFlags, skipping over cert auth logic." $ssl = $location.'system.webServer'.security.access.SslFlags - $sslFlag = "$($ssl.ToLower().Contains("ssl")) $(if(($ssl.ToLower().Contains("ssl128"))) { "(128-bit)" })".Trim() + $sslFlag = "$($ssl -contains "ssl") $(if(($ssl -contains "ssl128")) { "(128-bit)" })".Trim() } $iisVirtualDirectoriesDisplay.Add([PSCustomObject]@{ @@ -444,11 +444,11 @@ function Invoke-AnalyzerIISInformation { # only collect the ones that are valid, if not valid we will assume that the child web apps will point to it and can be misleading. $siteConfigPaths = $iisWebSiteConfigs | Where-Object { $_.Valid -eq $true -and $_.Exist -eq $true } | - ForEach-Object { $_.Location.ToLower() } + ForEach-Object { $_.Location } if ($null -ne $siteConfigPaths) { $missingWebApplicationConfigFile = $exchangeInformation.IISSettings.IISWebApplication | - Where-Object { $siteConfigPaths.Contains($_.ConfigurationFileInfo.Location.ToLower()) } + Where-Object { $siteConfigPaths -contains "$($_.ConfigurationFileInfo.Location)" } } # Missing config file should really only occur for SharedWebConfig files, as the web application would go back to the parent site. @@ -461,13 +461,13 @@ function Invoke-AnalyzerIISInformation { $binSearchFolders = (([xml]($_.Content)).configuration.appSettings.add | Where-Object { $_.key -eq "BinSearchFolders" }).value - $paths = $binSearchFolders.Split(";").Trim().ToLower() + $paths = $binSearchFolders.Split(";").Trim() $paths | ForEach-Object { Write-Verbose "BinSearchFolder: $($_)" } $installPath = $exchangeInformation.RegistryValues.MsiInstallPath foreach ($binTestPath in @("bin", "bin\CmdletExtensionAgents", "ClientAccess\Owa\bin")) { - $testPath = [System.IO.Path]::Combine($installPath, $binTestPath).ToLower() + $testPath = [System.IO.Path]::Combine($installPath, $binTestPath) Write-Verbose "Testing path: $testPath" - if (-not ($paths.Contains($testPath))) { + if (-not ($paths -contains $testPath)) { return $_ } } From 0fdd4814825f04f0f22a5d3d05f3275fb744f127 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 18 Dec 2023 12:47:57 -0600 Subject: [PATCH 07/15] Address issues with Remove type URL Rewrite Rule --- .../Invoke-AnalyzerIISInformation.ps1 | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 index aec0ec4c2c..85b311a791 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 @@ -385,7 +385,19 @@ function Invoke-AnalyzerIISInformation { $authentication = $authTypeSettings[$location.Path] if ($currentRewriteRules.Count -ne 0) { - $displayRewriteRules = ($currentRewriteRules.rule | Where-Object { $_.enabled -ne "false" }).name + # Need to loop through all the rules first to find the excluded rules + # then find the rules to display + $excludeRules = @() + foreach ($rule in $currentRewriteRules) { + $remove = $rule.Remove + + if ($null -ne $remove) { + $excludeRules += $remove.Name + } + } + + $displayRewriteRules = ($currentRewriteRules.rule | Where-Object { $_.enabled -ne "false" }).name | + Where-Object { $_ -notcontains $excludeRules } } if ($null -ne $ep) { @@ -486,8 +498,11 @@ function Invoke-AnalyzerIISInformation { if ($currentSection.Count -ne 0) { foreach ($rule in $currentSection.rule) { - # skip over disabled rules. - if ($rule.enabled -eq "false") { + if ($null -eq $rule) { + Write-Verbose "Rule is NULL skipping." + continue + } elseif ($rule.enabled -eq "false") { + # skip over disabled rules. Write-Verbose "skipping over disabled rule: $($rule.Name) for vDir '$key'" continue } From f1a870230f82316e582f54c14b10e6a535f35f78 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 18 Dec 2023 13:22:10 -0600 Subject: [PATCH 08/15] Add comment about IP Filtering being accurate --- .../HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 index 85b311a791..a6dfcd7286 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerIISInformation.ps1 @@ -378,6 +378,8 @@ function Invoke-AnalyzerIISInformation { Write-Verbose "Working on IIS Path: $($location.Path)" $sslFlag = [string]::Empty $displayRewriteRules = [string]::Empty + #TODO: This is not 100% accurate because you can have a disabled rule here. + # However, not sure how common this is going to be so going to ignore this for now. $ipFilterEnabled = $ipFilterSettings[$location.Path].Count -ne 0 $epValue = "None" $ep = $extendedProtectionConfiguration | Where-Object { $_.VirtualDirectoryName -eq $location.Path } From 34317bd6892ea3fc90e94da278120ead34f34d59 Mon Sep 17 00:00:00 2001 From: Lukas Sassl Date: Mon, 18 Dec 2023 21:10:59 +0100 Subject: [PATCH 09/15] Update .build/Build.ps1 Co-authored-by: Bill Long --- .build/Build.ps1 | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.build/Build.ps1 b/.build/Build.ps1 index 4116035b36..cdf31be9cb 100644 --- a/.build/Build.ps1 +++ b/.build/Build.ps1 @@ -139,14 +139,7 @@ if ($nonUnique.Count -gt 0) { $scriptVersions = @() -if ($IsMacOS -or - $IsLinux) { - Write-Host "We're running the build process on MacOS / Linux" - $disclaimer = [IO.File]::ReadAllLines("$((Get-Location).Path)/../LICENSE") -} else { - Write-Host "We're running the build process on Windows" - $disclaimer = [IO.File]::ReadAllLines("$PSScriptRoot\..\LICENSE") -} +$disclaimer = [IO.File]::ReadAllLines([IO.Path]::Combine($PSScriptRoot, "..", "LICENSE")) $documentedScriptFiles | ForEach-Object { $scriptName = [IO.Path]::GetFileName($_) From 72d3dbae4519d1488c9e559a09e94b05fd8275f0 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 18 Dec 2023 14:42:00 -0600 Subject: [PATCH 10/15] Fixed comment to work with Get-Help --- Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 b/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 index cd697be19a..8118a81b07 100644 --- a/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 @@ -2,6 +2,9 @@ # Licensed under the MIT License. <# +.SYNOPSIS + Pulls out URL Rewrite Rules from the web.config and applicationHost.config file to return a Hashtable of those settings. +.DESCRIPTION This is a function that is designed to pull out the URL Rewrite Rules that are set on a location of IIS. Because you can set it on an individual web.config file or the parent site(s), or the ApplicationHostConfig file for the location We need to check all locations to properly determine what is all set. From 01d74e89c55b7456002595119f0416adaca1cea7 Mon Sep 17 00:00:00 2001 From: lusassl-msft Date: Tue, 19 Dec 2023 11:31:48 +0100 Subject: [PATCH 11/15] Write out the OS on which the build process runs --- .build/Build.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.build/Build.ps1 b/.build/Build.ps1 index cdf31be9cb..307bbe6aae 100644 --- a/.build/Build.ps1 +++ b/.build/Build.ps1 @@ -17,6 +17,8 @@ Set-StrictMode -Version Latest . $PSScriptRoot\BuildFunctions\Get-ScriptDependencyTree.ps1 . $PSScriptRoot\BuildFunctions\Show-ScriptDependencyTree.ps1 +Write-Host "Build process is running on: Windows? $IsWindows - MacOS? $IsMacOS - Linux? $IsLinux" + $repoRoot = Get-Item "$PSScriptRoot\.." <# From f7b80afd96b7f271e15bbfad36840d19925723b2 Mon Sep 17 00:00:00 2001 From: Bill Long Date: Mon, 10 Oct 2022 15:39:08 -0500 Subject: [PATCH 12/15] Add Test-ExchangePropertyPermissions script --- .../.gitignore | 1 + .../GeneratePropertySetInfo.NotPublished.ps1 | 71 ++++++ .../Get-PropertySetInfo.ps1 | 34 +++ .../Test-ExchangePropertyPermissions.ps1 | 232 ++++++++++++++++++ .../Test-ExchangeSchema.ps1 | 80 ++++++ .../Get-ObjectTypeDisplayName.ps1 | 32 +++ .../Admin/Test-ExchangePropertyPermissions.md | 41 ++++ mkdocs.yml | 1 + 8 files changed, 492 insertions(+) create mode 100644 Admin/Test-ExchangePropertyPermissions/.gitignore create mode 100644 Admin/Test-ExchangePropertyPermissions/GeneratePropertySetInfo.NotPublished.ps1 create mode 100644 Admin/Test-ExchangePropertyPermissions/Get-PropertySetInfo.ps1 create mode 100644 Admin/Test-ExchangePropertyPermissions/Test-ExchangePropertyPermissions.ps1 create mode 100644 Admin/Test-ExchangePropertyPermissions/Test-ExchangeSchema.ps1 create mode 100644 Shared/ActiveDirectoryFunctions/Get-ObjectTypeDisplayName.ps1 create mode 100644 docs/Admin/Test-ExchangePropertyPermissions.md diff --git a/Admin/Test-ExchangePropertyPermissions/.gitignore b/Admin/Test-ExchangePropertyPermissions/.gitignore new file mode 100644 index 0000000000..6722cd96e7 --- /dev/null +++ b/Admin/Test-ExchangePropertyPermissions/.gitignore @@ -0,0 +1 @@ +*.xml diff --git a/Admin/Test-ExchangePropertyPermissions/GeneratePropertySetInfo.NotPublished.ps1 b/Admin/Test-ExchangePropertyPermissions/GeneratePropertySetInfo.NotPublished.ps1 new file mode 100644 index 0000000000..1245fbad8c --- /dev/null +++ b/Admin/Test-ExchangePropertyPermissions/GeneratePropertySetInfo.NotPublished.ps1 @@ -0,0 +1,71 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +$propertySets = @( + [PSCustomObject]@{ + Name = "Exchange-Information" + RightsGuid = [Guid]::Parse("1F298A89-DE98-47b8-B5CD-572AD53D267E") + MemberAttributes = New-Object System.Collections.ArrayList + }, + [PSCustomObject]@{ + Name = "Exchange-Personal-Information" + RightsGuid = [Guid]::Parse("B1B3A417-EC55-4191-B327-B72E33E38AF2") + MemberAttributes = New-Object System.Collections.ArrayList + }, + [PSCustomObject]@{ + Name = "Personal-Information" + RightsGuid = [Guid]::Parse("77B5B886-944A-11d1-AEBD-0000F80367C1") + MemberAttributes = New-Object System.Collections.ArrayList + }, + [PSCustomObject]@{ + Name = "Public-Information" + RightsGuid = [Guid]::Parse("E48D0154-BCF8-11D1-8702-00C04FB96050") + MemberAttributes = New-Object System.Collections.ArrayList + } +) + +$rootDSE = [ADSI]("LDAP://$([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)/RootDSE") +$schemaContainer = [ADSI]("LDAP://" + $rootDSE.schemaNamingContext) + +foreach ($propertySet in $propertySets) { + $rightsGuidByteString = "" + $propertySet.RightsGuid.ToByteArray() | ForEach-Object { $rightsGuidByteString += ("\$($_.ToString("X"))") } + $searcher = New-Object System.DirectoryServices.directorySearcher($schemaContainer, "(&(objectClass=attributeSchema)(attributeSecurityGuid=$rightsGuidByteString))") + $searcher.PageSize = 100 + $results = $searcher.FindAll() + foreach ($result in $results) { + [void]$propertySet.MemberAttributes.Add($result.Properties["cn"][0]) + } +} + +$getPropertySetInfoBuilder = New-Object System.Text.StringBuilder +[void]$getPropertySetInfoBuilder.AppendLine("# Copyright (c) Microsoft Corporation.") +[void]$getPropertySetInfoBuilder.AppendLine("# Licensed under the MIT License.") +[void]$getPropertySetInfoBuilder.AppendLine("") +[void]$getPropertySetInfoBuilder.AppendLine("# This is a generated function. Do not manually modify.") +[void]$getPropertySetInfoBuilder.AppendLine("function Get-PropertySetInfo {") +[void]$getPropertySetInfoBuilder.AppendLine(" [CmdletBinding()]") +[void]$getPropertySetInfoBuilder.AppendLine(" [OutputType([System.Object[]])]") +[void]$getPropertySetInfoBuilder.AppendLine(" param ()") +[void]$getPropertySetInfoBuilder.AppendLine("") +[void]$getPropertySetInfoBuilder.AppendLine(" # cSpell:disable") +[void]$getPropertySetInfoBuilder.AppendLine(" `$propertySetInfo = @(") +for ($i = 0; $i -lt $propertySets.Count; $i++) { + $propertySet = $propertySets[$i] + $memberAttributeString = [string]::Join(", ", ($propertySet.MemberAttributes | ForEach-Object { "`"$_`"" })) + [void]$getPropertySetInfoBuilder.AppendLine(" [PSCustomObject]@{") + [void]$getPropertySetInfoBuilder.AppendLine(" Name = `"$($propertySet.Name)`"") + [void]$getPropertySetInfoBuilder.AppendLine(" RightsGuid = [Guid]::Parse(`"$($propertySet.RightsGuid)`")") + [void]$getPropertySetInfoBuilder.AppendLine(" MemberAttributes = $memberAttributeString") + [void]$getPropertySetInfoBuilder.Append(" }") + if ($i + 1 -lt $propertySets.Count) { + [void]$getPropertySetInfoBuilder.AppendLine(",") + } +} +[void]$getPropertySetInfoBuilder.AppendLine(" )") +[void]$getPropertySetInfoBuilder.AppendLine(" # cSpell:enable") +[void]$getPropertySetInfoBuilder.AppendLine(" `$propertySetInfo") +[void]$getPropertySetInfoBuilder.AppendLine("}") +[void]$getPropertySetInfoBuilder.AppendLine("") + +Set-Content $PSScriptRoot\Get-PropertySetInfo.ps1 $getPropertySetInfoBuilder.ToString() diff --git a/Admin/Test-ExchangePropertyPermissions/Get-PropertySetInfo.ps1 b/Admin/Test-ExchangePropertyPermissions/Get-PropertySetInfo.ps1 new file mode 100644 index 0000000000..bee613b39f --- /dev/null +++ b/Admin/Test-ExchangePropertyPermissions/Get-PropertySetInfo.ps1 @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# This is a generated function. Do not manually modify. +function Get-PropertySetInfo { + [CmdletBinding()] + [OutputType([System.Object[]])] + param () + + # cSpell:disable + $propertySetInfo = @( + [PSCustomObject]@{ + Name = "Exchange-Information" + RightsGuid = [Guid]::Parse("1f298a89-de98-47b8-b5cd-572ad53d267e") + MemberAttributes = "ms-Exch-Assistant-Name", "ms-Exch-LabeledURI", "ms-Exch-ADC-Global-Names", "ms-Exch-Attribute-Certificate", "ms-Exch-AutoReply", "ms-Exch-AutoReply-Message", "ms-Exch-Deleted-Item-Flags", "ms-Exch-Deliv-Cont-Length", "ms-Exch-Deliver-And-Redirect", "ms-Exch-Enabled-Protocols", "ms-Exch-Expansion-Server-Name", "ms-Exch-Expiration-Time", "ms-Exch-Extension-Attribute-1", "ms-Exch-Extension-Attribute-10", "ms-Exch-Extension-Attribute-11", "ms-Exch-Extension-Attribute-12", "ms-Exch-Extension-Attribute-13", "ms-Exch-Extension-Attribute-14", "ms-Exch-Extension-Attribute-15", "ms-Exch-Extension-Attribute-2", "ms-Exch-Extension-Attribute-3", "ms-Exch-Extension-Attribute-4", "ms-Exch-Extension-Attribute-5", "ms-Exch-Extension-Attribute-6", "ms-Exch-Extension-Attribute-7", "ms-Exch-Extension-Attribute-8", "ms-Exch-Extension-Attribute-9", "ms-Exch-Folder-Pathname", "ms-Exch-Form-Data", "ms-Exch-Forwarding-Address", "ms-Exch-Hide-DL-Membership", "ms-Exch-Hide-From-Address-Lists", "ms-Exch-Home-MTA", "ms-Exch-Home-Server-Name", "ms-Exch-Internet-Encoding", "ms-Exch-Language", "ms-Exch-Language-ISO639", "ms-Exch-Mail-Nickname", "ms-Exch-MAPI-Recipient", "ms-Exch-MDB-Over-Hard-Quota-Limit", "ms-Exch-MDB-Over-Quota-Limit", "ms-Exch-MDB-Storage-Quota", "ms-Exch-MDB-Use-Defaults", "ms-Exch-OOF-Reply-To-Originator", "ms-Exch-POP-Character-Set", "ms-Exch-POP-Content-Format", "ms-Exch-Protocol-Settings", "ms-Exch-Replicated-Object-Version", "ms-Exch-Replication-Sensitivity", "ms-Exch-Replication-Signature", "ms-Exch-Report-To-Originator", "ms-Exch-Report-To-Owner", "ms-Exch-Security-Protocol", "ms-Exch-Submission-Cont-Length", "ms-Exch-Supported-Algorithms", "ms-Exch-Target-Address", "ms-Exch-Telephone-Assistant", "ms-Exch-Unmerged-Atts", "ms-Exch-X500-NC", "ms-Exch-Alt-Recipient", "ms-Exch-Home-MDB", "ms-Exch-Auth-Orig", "ms-Exch-DL-Mem-Submit-Perms", "ms-Exch-Unauth-Orig", "ms-Exch-DL-Mem-Reject-Perms", "ms-Exch-Mailbox-Guid", "ms-Exch-Mailbox-Security-Descriptor", "ms-Exch-Master-Account-Sid", "ms-Exch-Imported-From", "ms-Exch-Custom-Proxy-Addresses", "ms-Exch-Deliv-Ext-Cont-Types", "ms-Exch-Delivery-Mechanism", "ms-Exch-DL-Mem-Default", "ms-Exch-DL-Member-Rule", "ms-Exch-FB-URL", "ms-Exch-Heuristics", "ms-Exch-IM-ACL", "ms-Exch-IM-Address", "ms-Exch-IM-Meta-Physical-URL", "ms-Exch-IM-Physical-URL", "ms-Exch-IM-Virtual-Server", "ms-Exch-PF-Tree-Type", "ms-Exch-TUI-Password", "ms-Exch-TUI-Speed", "ms-Exch-TUI-Volume", "ms-Exch-Unmerged-Atts-Pt", "ms-Exch-Voice-Mailbox-ID", "ms-Exch-Auth-Orig-BL", "ms-Exch-DL-Mem-Submit-Perms-BL", "ms-Exch-Unauth-Orig-BL", "ms-Exch-DL-Mem-Reject-Perms-BL", "ms-Exch-Use-OAB", "ms-Exch-Previous-Account-Sid", "ms-Exch-Query-Base-DN", "ms-Exch-Recip-Limit", "ms-Exch-Resource-GUID", "ms-Exch-Extension-Data", "ms-Exch-AL-Object-Version", "ms-Exch-Controlling-Zone", "ms-Exch-KM-Server", "ms-Exch-Policy-Option-List", "ms-Exch-Purported-Search-UI", "ms-Exch-Resource-Properties", "ms-Exch-Alt-Recipient-BL", "ms-Exch-Public-Delegates-BL", "ms-Exch-Policies-Excluded", "ms-Exch-Policies-Included", "ms-Exch-Policy-Enabled", "ms-Exch-Proxy-Custom-Proxy", "ms-Exch-Inconsistent-State", "ms-Exch-Conference-Mailbox-BL", "ms-Exch-Mailbox-Url", "ms-Exch-Pf-Root-Url", "ms-Exch-User-Account-Control", "ms-Exch-Mailbox-Folder-Set", "ms-Exch-Oma-Admin-Extended-Settings", "ms-Exch-Oma-Admin-Wireless-Enable", "ms-Exch-RequireAuthToSendTo", "ms-Exch-IMAP-OWA-URL-Prefix-Override", "ms-Exch-Originating-Forest", "ms-Exch-Resource-Capacity", "ms-Exch-Resource-Display", "ms-Exch-Resource-Meta-Data", "ms-Exch-Resource-Search-Properties", "ms-Exch-UM-Audio-Codec", "ms-Exch-UM-Dtmf-Map", "ms-Exch-UM-Enabled-Flags", "ms-Exch-UM-Fax-Id", "ms-Exch-UM-List-In-Directory-Search", "ms-Exch-UM-Max-Greeting-Duration", "ms-Exch-UM-Operator-Number", "ms-Exch-UM-Pin-Policy-Account-Lockout-Failures", "ms-Exch-UM-Pin-Policy-Disallow-Common-Patterns", "ms-Exch-UM-Pin-Policy-Expiry-Days", "ms-Exch-UM-Pin-Policy-Min-Password-Length", "ms-Exch-UM-Spoken-Name", "ms-Exch-Mailbox-Template-Link", "ms-Exch-UM-Template-Link", "ms-Exch-UM-Recipient-Dial-Plan-Link", "ms-Exch-External-OOF-Options", "ms-Exch-MDB-Rules-Quota", "ms-Exch-Mobile-Allowed-Device-IDs", "ms-Exch-Mobile-Debug-Logging", "ms-Exch-Mobile-Mailbox-Policy-Link", "ms-Exch-Mailbox-OAB-Virtual-Directories-Link", "ms-Exch-Server-Admin-Delegation-BL", "ms-Exch-Accepted-Domain-Flags", "ms-Exch-Accepted-Domain-Name", "ms-Exch-ELC-Expiry-Suspension-End", "ms-Exch-ELC-Expiry-Suspension-Start", "ms-Exch-Master-Account-History", "ms-Exch-Message-Hygiene-SCL-Junk-Threshold", "ms-Exch-Mobile-Mailbox-Flags", "ms-Exch-Recipient-Display-Type", "ms-Exch-Recipient-Type-Details", "ms-Exch-UM-Server-Writable-Flags", "ms-Exch-User-Culture", "ms-Exch-Version", "ms-Exch-HAB-Show-In-Departments", "ms-Exch-Max-Blocked-Senders", "ms-Exch-Max-Safe-Senders", "ms-Exch-Query-Filter-Metadata", "ms-Exch-CU", "ms-Exch-OU-Root", "ms-Exch-Sender-Hint-Large-Audience-Threshold", "ms-Exch-Sender-Hint-Translations", "ms-Exch-Sender-Hints-Enabled", "ms-Exch-UM-Enabled-Flags-2", "ms-Exch-Policy-Tag-Link", "ms-Exch-Policy-Tag-Link-BL", "ms-Exch-Arbitration-Mailbox", "ms-Exch-Enable-Moderation", "ms-Exch-Group-Depart-Restriction", "ms-Exch-Group-Join-Restriction", "ms-Exch-Moderation-Flags", "ms-Exch-OWA-Policy", "ms-Exch-Windows-Live-ID", "ms-Exch-Approval-Application-Link", "ms-Exch-Co-Managed-By-Link", "ms-Exch-Co-Managed-Objects-BL", "ms-Exch-Moderated-By-Link", "ms-Exch-Moderated-Objects-BL", "ms-Exch-Provisioning-Flags", "ms-Exch-Throttling-Policy-DN", "ms-Exch-Parent-Plan-Link", "ms-Exch-Bypass-Moderation-Link", "ms-Exch-Bypass-Moderation-BL", "ms-Exch-Bypass-Moderation-From-DL-Members-Link", "ms-Exch-Bypass-Moderation-From-DL-Members-BL", "ms-Exch-Reseller", "ms-Exch-Service-Plan", "ms-Exch-User-BL", "ms-Exch-UM-Mailbox-OVA-Language", "ms-Exch-Calendar-Repair-Disabled", "ms-Exch-Control-Point-Flags", "ms-Exch-Mailbox-Move-Flags", "ms-Exch-Mailbox-Move-Remote-Host-Name", "ms-Exch-Mailbox-Move-Status", "ms-Exch-Mailbox-Plan-Type", "ms-Exch-RMS-Licensing-Location-Url", "ms-Exch-Sync-Accounts-Policy-DN", "ms-Exch-Text-Messaging-State", "ms-Exch-UM-Load-Balancer-FQDN", "ms-Exch-Mailbox-Move-Target-MDB-Link", "ms-Exch-Mailbox-Move-Target-MDB-BL", "ms-Exch-Dirsync-ID", "ms-Exch-Management-Site-Link", "ms-Exch-UM-Audio-Codec-2", "ms-Exch-UM-Business-Location", "ms-Exch-UM-Business-Name", "ms-Exch-UM-Default-Mailbox", "ms-Exch-UM-Default-Outbound-Calling-Line-ID", "ms-Exch-UM-Week-Start-Day", "ms-Exch-Dirsync-Id-Source-Attribute", "ms-Exch-Galsync-Disable-Live-Id-On-Remove", "ms-Exch-Galsync-Federated-Tenant-Source-Attribute", "ms-Exch-Galsync-Last-Sync-Run", "ms-Exch-Galsync-Password-File-Path", "ms-Exch-Galsync-Provisioning-Domain", "ms-Exch-Galsync-Reset-Password-On-Next-Logon", "ms-Exch-Galsync-Schedule", "ms-Exch-Galsync-Source-Active-Directory-Schema-Version", "ms-Exch-Galsync-Wlid-Use-Smtp-Primary", "ms-Exch-MDB-Copy-Parent-Class", "ms-Exch-RBAC-Policy-Flags", "ms-Exch-UM-Forwarding-Address-Template", "ms-Exch-RBAC-Policy-Link", "ms-Exch-Config-Filter", "ms-Exch-Org-Federated-Mailbox", "ms-Exch-Previous-Home-MDB", "ms-Exch-Smtp-Max-Messages-Per-Connection", "ms-Exch-Availability-Per-User-Account-BL", "ms-Exch-Availability-Org-Wide-Account-BL", "ms-Exch-OWA-Transcoding-File-Types-BL", "ms-Exch-OWA-Allowed-File-Types-BL", "ms-Exch-OWA-Allowed-Mime-Types-BL", "ms-Exch-OWA-Force-Save-File-Types-BL", "ms-Exch-OWA-Force-Save-MIME-Types-BL", "ms-Exch-OWA-Blocked-File-Types-BL", "ms-Exch-OWA-Blocked-MIME-Types-BL", "ms-Exch-OWA-Remote-Documents-Allowed-Servers-BL", "ms-Exch-OWA-Remote-Documents-Blocked-Servers-BL", "ms-Exch-OWA-Transcoding-Mime-Types-BL", "ms-Exch-SMTP-Receive-Default-Accepted-Domain-BL", "ms-Exch-Mobile-Remote-Documents-Allowed-Servers-BL", "ms-Exch-Mobile-Remote-Documents-Blocked-Servers-BL", "ms-Exch-Mobile-Remote-Documents-Internal-Domain-Suffix-List-BL", "ms-Exch-Server-Site-BL", "ms-Exch-Organizations-Global-Address-Lists-BL", "ms-Exch-Organizations-Address-Book-Roots-BL", "ms-Exch-Organizations-Template-Roots-BL", "ms-Exch-Supervision-User-BL", "ms-Exch-RBAC-Policy-BL", "ms-Exch-Allow-Cross-Site-RPC-Client-Access", "ms-Exch-Data-Move-Replication-Constraint", "ms-Exch-Device-Access-Rule-Characteristic", "ms-Exch-Device-Access-Rule-Query-String", "ms-Exch-Dumpster-Quota", "ms-Exch-Dumpster-Warning-Quota", "ms-Exch-Edge-Sync-Advanced-Configuration", "ms-Exch-Edge-Sync-EHF-Backup-Lease-Location", "ms-Exch-Edge-Sync-EHF-Password", "ms-Exch-Edge-Sync-EHF-Primary-Lease-Location", "ms-Exch-Edge-Sync-EHF-Provisioning-URL", "ms-Exch-Edge-Sync-EHF-Reseller-ID", "ms-Exch-Edge-Sync-EHF-User-Name", "ms-Exch-Edge-Sync-Retry-Count", "ms-Exch-ESE-Param-Cache-Priority", "ms-Exch-ESE-Param-Replay-Background-Database-Maintenance", "ms-Exch-ESE-Param-Replay-Cache-Priority", "ms-Exch-ESE-Param-Replay-Checkpoint-Depth-Max", "ms-Exch-Foreign-Group-SID", "ms-Exch-Host-Server-Name", "ms-Exch-Mailbox-Move-Batch-Name", "ms-Exch-Max-Active-Mailbox-Databases", "ms-Exch-MDB-Name", "ms-Exch-Mobile-Access-Control", "ms-Exch-Mobile-Admin-Recipients", "ms-Exch-Mobile-User-Mail-Insert", "ms-Exch-Object-Count-Quota", "ms-Exch-POP-IMAP-External-Connection-Settings", "ms-Exch-POP-IMAP-Internal-Connection-Settings", "ms-Exch-RCA-Throttling-Policy-State", "ms-Exch-Sync-Accounts-Successive-Poison-Items-Threshold", "ms-Exch-Sync-Hub-Health-Log-Age-Quota-In-Hours", "ms-Exch-Sync-Hub-Health-Log-Directory-Size-Quota", "ms-Exch-Sync-Hub-Health-Log-File-Path", "ms-Exch-Sync-Hub-Health-Log-Per-File-Size-Quota", "ms-Exch-Sync-Mailbox-Health-Log-Age-Quota-In-Hours", "ms-Exch-Sync-Mailbox-Health-Log-Directory-Size-Quota", "ms-Exch-Sync-Mailbox-Health-Log-File-Path", "ms-Exch-Sync-Mailbox-Health-Log-Per-File-Size-Quota", "ms-Exch-Tenant-Perimeter-Settings-Flags", "ms-Exch-Tenant-Perimeter-Settings-Gateway-IP-Addresses", "ms-Exch-Tenant-Perimeter-Settings-Internal-Server-IP-Addresses", "ms-Exch-Tenant-Perimeter-Settings-Org-ID", "ms-Exch-Third-Party-Synchronous-Replication", "ms-Exch-UM-Certificate-Thumbprint", "ms-Exch-UM-Startup-Mode", "ms-Exch-Voice-Mail-Preview-Partner-Address", "ms-Exch-Voice-Mail-Preview-Partner-Assigned-ID", "ms-Exch-Voice-Mail-Preview-Partner-Max-Delivery-Delay", "ms-Exch-Voice-Mail-Preview-Partner-Max-Message-Duration", "ms-Exch-Mailbox-Move-Source-MDB-Link", "ms-Exch-Mailbox-Move-Source-MDB-BL", "ms-Exch-RMS-Computer-Accounts-Link", "ms-Exch-RMS-Computer-Accounts-BL", "ms-Exch-Intended-Mailbox-Plan-Link", "ms-Exch-Intended-Mailbox-Plan-BL", "ms-Exch-2003-Url", "ms-Exch-Legacy-Redirect-Type", "ms-Exch-License-Token", "ms-Exch-Mailbox-Folder-Set-2", "ms-Exch-Object-ID", "ms-Exch-Content-Conversion-Settings", "ms-Exch-IMAP4-Settings", "ms-Exch-Management-Settings", "ms-Exch-Mobile-Settings", "ms-Exch-OWA-Settings", "ms-Exch-POP3-Settings", "ms-Exch-Transport-Inbound-Settings", "ms-Exch-Transport-Outbound-Settings", "ms-Org-Group-Subtype-Name", "ms-Org-Is-Organizational-Group", "ms-Org-Other-Display-Names", "ms-Org-Leaders", "ms-Org-Leaders-BL", "ms-Exch-Ews-Application-Access-Policy", "ms-Exch-Ews-Enabled", "ms-Exch-Ews-Exceptions", "ms-Exch-Ews-Well-Known-Application-Policies", "ms-Exch-Archive-Address", "ms-Exch-Archive-Status", "ms-Exch-Authoritative-Policy-Tag-GUID", "ms-Exch-Authoritative-Policy-Tag-Note", "ms-Exch-AV-Authentication-Service", "ms-Exch-Capability-Identifiers", "ms-Exch-Distribution-Group-Default-OU", "ms-Exch-Distribution-Group-Name-Blocked-Words-List", "ms-Exch-Distribution-Group-Naming-Policy", "ms-Exch-External-Directory-Object-Id", "ms-Exch-External-Directory-Organization-Id", "ms-Exch-Last-Exchange-Changed-Time", "ms-Exch-Mailbox-Move-File-Path", "ms-Exch-Mailbox-Move-Request-Guid", "ms-Exch-MSO-Forward-Sync-Non-Recipient-Cookie", "ms-Exch-MSO-Forward-Sync-Recipient-Cookie", "ms-Exch-OWA-IM-Certificate-Thumbprint", "ms-Exch-OWA-IM-Server-Name", "ms-Exch-Pop-Imap-Log-File-Path", "ms-Exch-Pop-Imap-Log-File-Rollover-Frequency", "ms-Exch-Pop-Imap-Per-Log-File-Size-Quota", "ms-Exch-Remote-Recipient-Type", "ms-Exch-SIP-Access-Service", "ms-Exch-UM-Dial-Plan-Timezone", "ms-Exch-When-Mailbox-Created", "ms-Exch-Default-Public-MDB", "ms-Exch-Default-Public-MDB-BL", "ms-Exch-Mailbox-Move-Source-User-Link", "ms-Exch-Mailbox-Move-Source-User-BL", "ms-Exch-Mailbox-Move-Storage-MDB-Link", "ms-Exch-Mailbox-Move-Storage-MDB-BL", "ms-Exch-Mailbox-Move-Target-User-Link", "ms-Exch-Mailbox-Move-Target-User-BL", "ms-Exch-Activity-Based-Authentication-Timeout-Interval", "ms-Exch-Anonymous-Throttling-Policy-State", "ms-Exch-Edge-Sync-Connector-Version", "ms-Exch-Generic-Forwarding-Address", "ms-Exch-Partner-Group-ID", "ms-Exch-Shared-Config-Service-Plan-Tag", "ms-Exch-Shared-Identity-Server-Box-RAC", "ms-Exch-TPD-CSP-Name", "ms-Exch-TPD-CSP-Type", "ms-Exch-TPD-Display-Name", "ms-Exch-TPD-Extranet-Certification-Url", "ms-Exch-TPD-Extranet-Licensing-Url", "ms-Exch-TPD-Flags", "ms-Exch-TPD-Intranet-Certification-Url", "ms-Exch-TPD-Intranet-Licensing-Url", "ms-Exch-TPD-Key-Container-Name", "ms-Exch-TPD-Key-ID", "ms-Exch-TPD-Key-IDType", "ms-Exch-TPD-Key-Number", "ms-Exch-TPD-Private-Key", "ms-Exch-TPD-SLC-Certificate-Chain", "ms-Exch-TPD-Templates", "ms-Exch-Transport-Reseller-Intra-Tenant-Mail-Content-Type", "ms-Exch-Transport-Reseller-Settings-Inbound-Gateway-ID", "ms-Exch-Transport-Reseller-Settings-Link", "ms-Exch-Transport-Reseller-Settings-Outbound-Gateway-ID", "ms-Exch-UM-Source-Forest-Policy-Names", "ms-Exch-Shared-Config-Link", "ms-Exch-Shared-Config-BL", "ms-Exch-Active-Instance-Sleep-Interval", "ms-Exch-Assistants-Throttle-Workcycle", "ms-Exch-Community-URL", "ms-Exch-Community-URL-Enabled", "ms-Exch-ESE-Param-Background-Database-Maintenance-Delay", "ms-Exch-ESE-Param-Background-Database-Maintenance-Interval-Max", "ms-Exch-ESE-Param-Background-Database-Maintenance-Interval-Min", "ms-Exch-ESE-Param-Background-Database-Maintenance-Serialization", "ms-Exch-ESE-Param-Hung-IO-Action", "ms-Exch-ESE-Param-Hung-IO-Threshold", "ms-Exch-ESE-Param-Pre-Read-IO-Max", "ms-Exch-ESE-Param-Replay-Background-Database-Maintenance-Delay", "ms-Exch-ESE-Param-Replay-Pre-Read-IO-Max", "ms-Exch-Intended-Service-Plan", "ms-Exch-MRS-Request-Type", "ms-Exch-Notification-Address", "ms-Exch-Notification-Enabled", "ms-Exch-Passive-Instance-Sleep-Interval", "ms-Exch-Sync-Daemon-Max-Version", "ms-Exch-Sync-Daemon-Min-Version", "ms-Exch-Transport-Intra-Tenant-Mail-Content-Type", "ms-Exch-Transport-Partner-Connector-Domain", "ms-Exch-Transport-Partner-Routing-Domain", "ms-Exch-Audit-Admin", "ms-Exch-Audit-Delegate", "ms-Exch-Audit-Delegate-Admin", "ms-Exch-Audit-Owner", "ms-Exch-Bypass-Audit", "ms-Exch-Interrupt-User-On-Audit-Failure", "ms-Exch-IRM-Log-Max-Age", "ms-Exch-IRM-Log-Max-Directory-Size", "ms-Exch-IRM-Log-Max-File-Size", "ms-Exch-IRM-Log-Path", "ms-Exch-Is-MSO-Dirsync-Enabled", "ms-Exch-Is-MSO-Dirsynced", "ms-Exch-Mailbox-Audit-Enable", "ms-Exch-Mailbox-Audit-Log-Age-Limit", "ms-Exch-Mobile-OTA-Notification-Mail-Insert", "ms-Exch-On-Premise-Object-Guid", "ms-Exch-Shadow-Assistant-Name", "ms-Exch-Shadow-C", "ms-Exch-Shadow-Co", "ms-Exch-Shadow-Country-Code", "ms-Exch-Shadow-Department", "ms-Exch-Shadow-Display-Name", "ms-Exch-Shadow-Facsimile-Telephone-Number", "ms-Exch-Shadow-Given-Name", "ms-Exch-Shadow-Home-Phone", "ms-Exch-Shadow-Info", "ms-Exch-Shadow-L", "ms-Exch-Shadow-Mail-Nickname", "ms-Exch-Shadow-Mobile", "ms-Exch-Shadow-Other-Facsimile-Telephone", "ms-Exch-Shadow-Other-Home-Phone", "ms-Exch-Shadow-Other-Telephone", "ms-Exch-Shadow-Pager", "ms-Exch-Shadow-Physical-Delivery-Office-Name", "ms-Exch-Shadow-Postal-Code", "ms-Exch-Shadow-Proxy-Addresses", "ms-Exch-Shadow-Sn", "ms-Exch-Shadow-St", "ms-Exch-Shadow-Street-Address", "ms-Exch-Shadow-Telephone-Assistant", "ms-Exch-Shadow-Telephone-Number", "ms-Exch-Shadow-Title", "ms-Exch-Shadow-Windows-Live-ID", "ms-Exch-Shadow-WWW-Home-Page", "ms-Exch-SMTP-Extended-Protection-Policy", "ms-Exch-Mailbox-Move-Source-Archive-MDB-Link", "ms-Exch-Mailbox-Move-Source-Archive-MDB-BL", "ms-Exch-Mailbox-Move-Target-Archive-MDB-Link", "ms-Exch-Mailbox-Move-Target-Archive-MDB-BL", "ms-Exch-Address-Book-Flags", "ms-Exch-Dirsync-Source-Object-Class", "ms-Exch-Edge-Sync-EHF-Flags", "ms-Exch-Fed-Target-OWA-URL", "ms-Exch-Mailbox-Audit-Last-Admin-Access", "ms-Exch-Mailbox-Audit-Last-Delegate-Access", "ms-Exch-Mailbox-Audit-Last-External-Access", "ms-Exch-Migration-Log-Age-Quota-In-Hours", "ms-Exch-Migration-Log-Directory-Size-Quota", "ms-Exch-Migration-Log-Extension-Data", "ms-Exch-Migration-Log-Log-File-Path", "ms-Exch-Migration-Log-Logging-Level", "ms-Exch-Migration-Log-Per-File-Size-Quota", "ms-Exch-MSO-Forward-Sync-Async-Operation-Ids", "ms-Exch-Previous-Mailbox-Guid", "ms-Exch-SIP-SBC-Service", "ms-Exch-Smtp-Receive-Tls-Domain-Capabilities", "ms-Exch-Smtp-Send-Ndr-Level", "ms-Exch-Smtp-Send-Tls-Domain", "ms-Exch-Target-Server-Admins", "ms-Exch-Target-Server-Partner-Admins", "ms-Exch-Target-Server-Partner-View-Only-Admins", "ms-Exch-Target-Server-View-Only-Admins", "ms-Exch-Minor-Partner-Id", "ms-Exch-Mobile-OTA-Notification-Mail-Insert-2", "ms-Exch-Reconciliation-Cookies", "ms-Exch-Responsible-For-Sites", "ms-Exch-Shadow-Manager-Link", "ms-Exch-Supported-Shared-Config-Link", "ms-Exch-Supported-Shared-Config-BL", "ms-Exch-Calculated-Target-Address", "ms-Exch-Deletion-Period", "ms-Exch-Objects-Deleted-This-Period", "ms-Exch-Shadow-Company", "ms-Exch-Shadow-Initials", "ms-Exch-MSO-Forward-Sync-Replay-List", "ms-Exch-OWA-Failback-URL", "ms-Exch-Admin-Audit-Log-Excluded-Cmdlets", "ms-Exch-Countries", "ms-Exch-Usage-Location", "ms-Exch-Extended-Protection-SPNList", "ms-Exch-Migration-Log-Directory-Size-Quota-Large", "ms-Exch-PopImap-Extended-Protection-Policy", "ms-Exch-Dirsync-Authority-Metadata", "ms-Exch-Dirsync-Status", "ms-Exch-Dirsync-Status-Ack", "ms-Exch-Edge-Sync-Config-Flags", "ms-Exch-Is-Dirsync-Status-Pending", "ms-Exch-Localization-Flags", "ms-Exch-RoleGroup-Type", "ms-Exch-Coexistence-Domains", "ms-Exch-Coexistence-External-IP-Addresses", "ms-Exch-Coexistence-Feature-Flags", "ms-Exch-Coexistence-On-Premises-Smart-Host", "ms-Exch-Coexistence-Secure-Mail-Certificate-Thumbprint", "ms-Exch-Coexistence-Servers", "ms-Exch-Coexistence-Transport-Servers", "ms-Exch-Content-Byte-Encoder-Type-For-7-Bit-Charsets", "ms-Exch-Content-Preferred-Internet-Code-Page-For-Shift-Jis", "ms-Exch-Content-Required-Char-Set-Coverage", "ms-Exch-Dir-Sync-Service-Instance", "ms-Exch-Extension-Attribute-16", "ms-Exch-Extension-Attribute-17", "ms-Exch-Extension-Attribute-18", "ms-Exch-Extension-Attribute-19", "ms-Exch-Extension-Attribute-20", "ms-Exch-Extension-Attribute-21", "ms-Exch-Extension-Attribute-22", "ms-Exch-Extension-Attribute-23", "ms-Exch-Extension-Attribute-24", "ms-Exch-Extension-Attribute-25", "ms-Exch-Extension-Attribute-26", "ms-Exch-Extension-Attribute-27", "ms-Exch-Extension-Attribute-28", "ms-Exch-Extension-Attribute-29", "ms-Exch-Extension-Attribute-30", "ms-Exch-Extension-Attribute-31", "ms-Exch-Extension-Attribute-32", "ms-Exch-Extension-Attribute-33", "ms-Exch-Extension-Attribute-34", "ms-Exch-Extension-Attribute-35", "ms-Exch-Extension-Attribute-36", "ms-Exch-Extension-Attribute-37", "ms-Exch-Extension-Attribute-38", "ms-Exch-Extension-Attribute-39", "ms-Exch-Extension-Attribute-40", "ms-Exch-Extension-Attribute-41", "ms-Exch-Extension-Attribute-42", "ms-Exch-Extension-Attribute-43", "ms-Exch-Extension-Attribute-44", "ms-Exch-Extension-Attribute-45", "ms-Exch-Extension-Custom-Attribute-1", "ms-Exch-Extension-Custom-Attribute-2", "ms-Exch-Extension-Custom-Attribute-3", "ms-Exch-Extension-Custom-Attribute-4", "ms-Exch-Extension-Custom-Attribute-5", "ms-Exch-External-Directory-Object-Class", "ms-Exch-Mailbox-Database-Transport-Flags", "ms-Exch-Max-Concurrent-Migrations", "ms-Exch-Migration-Flags", "ms-Exch-MRS-Proxy-Flags", "ms-Exch-MRS-Proxy-Max-Connections", "ms-Exch-MSO-Forward-Sync-Divergence-Count", "ms-Exch-MSO-Forward-Sync-Divergence-Timestamp", "ms-Exch-Organization-Upgrade-Policy-Date", "ms-Exch-Organization-Upgrade-Policy-Enabled", "ms-Exch-Organization-Upgrade-Policy-MaxMailboxes", "ms-Exch-Organization-Upgrade-Policy-Priority", "ms-Exch-Organization-Upgrade-Policy-Source-Version", "ms-Exch-Organization-Upgrade-Policy-Status", "ms-Exch-Organization-Upgrade-Policy-Target-Version", "ms-Exch-OWA-Set-Photo-URL", "ms-Exch-Recipient-SoftDeleted-Status", "ms-Exch-When-Soft-Deleted-Time", "ms-Exch-MSO-Forward-Sync-Divergence-Related-Object-Link", "ms-Exch-Organization-Upgrade-Policy-Link", "ms-Exch-Organization-Upgrade-Policy-BL", "ms-Exch-Address-Book-Policy-Link", "ms-Exch-Address-Book-Policy-BL", "ms-Exch-Address-Lists-Link", "ms-Exch-Address-Lists-BL", "ms-Exch-Global-Address-List-Link", "ms-Exch-Global-Address-List-BL", "ms-Exch-Offline-Address-Book-Link", "ms-Exch-Offline-Address-Book-BL", "ms-Exch-All-Room-List-Link", "ms-Exch-All-Room-List-BL", "ms-Exch-Default-Public-Folder-Mailbox", "ms-Exch-Forest-Mode-Flag", "ms-Exch-Workload-Classification", "ms-Exch-Workload-Management-Is-Enabled", "ms-Exch-Workload-Type", "ms-Exch-Workload-Management-Policy-Link", "ms-Exch-Workload-Management-Policy-BL", "ms-Exch-Customer-Expectation-Critical", "ms-Exch-Customer-Expectation-Overloaded", "ms-Exch-Customer-Expectation-Underloaded", "ms-Exch-Discretionary-Critical", "ms-Exch-Discretionary-Overloaded", "ms-Exch-Discretionary-Underloaded", "ms-Exch-Internal-Maintenance-Critical", "ms-Exch-Internal-Maintenance-Overloaded", "ms-Exch-Internal-Maintenance-Underloaded", "ms-Exch-Resource-Type", "ms-Exch-Urgent-Critical", "ms-Exch-Urgent-Overloaded", "ms-Exch-Urgent-Underloaded", "ms-Exch-Device-Client-Type", "ms-Exch-Malware-Filtering-Defer-Attempts", "ms-Exch-Malware-Filtering-Defer-Wait-Time", "ms-Exch-Malware-Filtering-Flags", "ms-Exch-Malware-Filtering-Primary-Update-Path", "ms-Exch-Malware-Filtering-Secondary-Update-Path", "ms-Exch-Malware-Filtering-Update-Frequency", "ms-Exch-Malware-Filtering-Update-Timeout", "ms-Exch-Team-Mailbox-Expiration", "ms-Exch-Team-Mailbox-Expiry-Days", "ms-Exch-Team-Mailbox-Owners", "ms-Exch-Team-Mailbox-SharePoint-Linked-By", "ms-Exch-Team-Mailbox-SharePoint-Url", "ms-Exch-Team-Mailbox-Show-In-Client-List", "ms-Exch-Account-Forest-Link", "ms-Exch-Account-Forest-BL", "ms-Exch-Trusted-Domain-Link", "ms-Exch-Trusted-Domain-BL", "ms-Exch-Archive-Database-Link-SL", "ms-Exch-Disabled-Archive-Database-Link-SL", "ms-Exch-Fed-Delegation-Trust-SL", "ms-Exch-Home-MDB-SL", "ms-Exch-Home-MTA-SL", "ms-Exch-Mailbox-Move-Source-Archive-MDB-Link-SL", "ms-Exch-Mailbox-Move-Source-MDB-Link-SL", "ms-Exch-Mailbox-Move-Storage-MDB-Link-SL", "ms-Exch-Mailbox-Move-Target-Archive-MDB-Link-SL", "ms-Exch-Mailbox-Move-Target-MDB-Link-SL", "ms-Exch-Malware-Filter-Config-Alert-Text", "ms-Exch-Malware-Filter-Config-External-Body", "ms-Exch-Malware-Filter-Config-External-Subject", "ms-Exch-Malware-Filter-Config-Flags", "ms-Exch-Malware-Filter-Config-From-Address", "ms-Exch-Malware-Filter-Config-From-Name", "ms-Exch-Malware-Filter-Config-Internal-Body", "ms-Exch-Malware-Filter-Config-Internal-Subject", "ms-Exch-Management-Site-Link-SL", "ms-Exch-Off-Line-AB-Server-SL", "ms-Exch-Organization-Upgrade-Policy-Link-SL", "ms-Exch-Previous-Archive-Database-SL", "ms-Exch-Previous-Home-MDB-SL", "ms-Exch-RMS-Computer-Accounts-Link-SL", "ms-Exch-Spam-Add-Header", "ms-Exch-Spam-Asf-Settings", "ms-Exch-Spam-Asf-Test-Bcc-Address", "ms-Exch-Spam-False-Positive-Cc", "ms-Exch-Spam-Flags", "ms-Exch-Spam-Modify-Subject", "ms-Exch-Spam-Outbound-Spam-Cc", "ms-Exch-Spam-Redirect-Address", "ms-Exch-Transport-Reseller-Settings-Link-SL", "ms-Exch-Hygiene-Configuration-Link", "ms-Exch-Accepted-Domain-BL", "ms-Exch-Hygiene-Configuration-Malware-BL", "ms-Exch-Hosted-Content-Filter-Config-Link", "ms-Exch-Hygiene-Configuration-Spam-BL", "ms-Exch-Auto-DAG-Param-Database-Copies-Per-Database", "ms-Exch-Auto-DAG-Param-Database-Copies-Per-Volume", "ms-Exch-Auto-DAG-Param-Database-Copy-Flags", "ms-Exch-Auto-DAG-Param-Database-Flags", "ms-Exch-Auto-DAG-Param-Databases-Root-Folder-Path", "ms-Exch-Auto-DAG-Param-Failed-Volumes-Root-Folder-Path", "ms-Exch-Auto-DAG-Param-Flags", "ms-Exch-Auto-DAG-Param-Server-Flags", "ms-Exch-Auto-DAG-Param-Total-Number-Of-Databases", "ms-Exch-Auto-DAG-Param-Total-Number-Of-Servers", "ms-Exch-Auto-DAG-Param-Volumes-Root-Folder-Path", "ms-Exch-Auto-DAG-Schema-Version", "ms-Exch-MDB-Availability-Group-Replication-Port", "ms-Exch-Server-Fault-Zone", "ms-Exch-Smtp-Receive-Role", "ms-Exch-SMTP-Receive-Sender-Domain", "ms-Exch-Spam-Allowed-IP-Ranges", "ms-Exch-Spam-Blocked-IP-Ranges", "ms-Exch-Transport-Rule-State", "ms-Exch-Group-External-Member-Count", "ms-Exch-Group-Member-Count", "ms-Exch-Organization-Flags-2", "ms-Exch-RMSOnline-Certification-Location-Url", "ms-Exch-RMSOnline-Key-Sharing-Location-Url", "ms-Exch-RMSOnline-Licensing-Location-Url", "ms-Exch-Throttling-Policy-Flags", "ms-Exch-Malware-Filter-Config-External-Sender-Admin-Address", "ms-Exch-Malware-Filter-Config-Internal-Sender-Admin-Address", "ms-Exch-Malware-Filtering-Scan-Timeout", "ms-Exch-Spam-Country-Block-List", "ms-Exch-Spam-Language-Block-List", "ms-Exch-Spam-Notify-Outbound-Recipients", "ms-Exch-Auth-App-Secret", "ms-Exch-Auth-Application-Identifier", "ms-Exch-Auth-Auth-Server-Type", "ms-Exch-Auth-Authorization-Url", "ms-Exch-Auth-Certificate-Data", "ms-Exch-Auth-Certificate-Thumbprint", "ms-Exch-Auth-Flags", "ms-Exch-Auth-Issuer-Name", "ms-Exch-Auth-Issuing-Url", "ms-Exch-Auth-Linked-Account", "ms-Exch-Auth-Metadata-Url", "ms-Exch-Auth-Realm", "ms-Exch-Mailflow-Policy-Countries", "ms-Exch-Mailflow-Policy-Keywords", "ms-Exch-Mailflow-Policy-Publisher-Name", "ms-Exch-Mailflow-Policy-Transport-Rules-Template-Xml", "ms-Exch-Mailflow-Policy-Version", "ms-Exch-Public-Folder-EntryId", "ms-Exch-Public-Folder-Mailbox", "ms-Exch-Public-Folder-Smtp-Address", "ms-Exch-Spam-Digest-Frequency", "ms-Exch-Spam-Quarantine-Retention", "ms-Exch-Transport-MaxRetriesForLocalSiteShadow", "ms-Exch-Transport-MaxRetriesForRemoteSiteShadow", "ms-Exch-Transport-Rule-Immutable-Id", "ms-Exch-WAC-Discovery-Endpoint", "ms-Exch-Anonymous-Throttling-Policy-State-Ex", "ms-Exch-Canary-Data-0", "ms-Exch-Canary-Data-1", "ms-Exch-Canary-Data-2", "ms-Exch-Correlation-Id", "ms-Exch-EAS-Throttling-Policy-State-Ex", "ms-Exch-EWS-Throttling-Policy-State-Ex", "ms-Exch-General-Throttling-Policy-State-Ex", "ms-Exch-IMAP-Throttling-Policy-State-Ex", "ms-Exch-OWA-Throttling-Policy-State-Ex", "ms-Exch-POP-Throttling-Policy-State-Ex", "ms-Exch-Powershell-Throttling-Policy-State-Ex", "ms-Exch-RCA-Throttling-Policy-State-Ex", "ms-Exch-Relocate-Tenant-Completion-Target-Vector", "ms-Exch-Relocate-Tenant-Flags", "ms-Exch-Relocate-Tenant-Safe-Lockdown-Schedule", "ms-Exch-Relocate-Tenant-Source-Forest", "ms-Exch-Relocate-Tenant-Start-Lockdown", "ms-Exch-Relocate-Tenant-Start-Retired", "ms-Exch-Relocate-Tenant-Start-Sync", "ms-Exch-Relocate-Tenant-Status", "ms-Exch-Relocate-Tenant-Target-Forest", "ms-Exch-Relocate-Tenant-Transition-Counter", "ms-Exch-Sync-Cookie", "ms-Exch-Adfs-Authentication-Raw-Configuration", "ms-Exch-Service-End-Point-URL", "ms-Exch-Sync-Service-Instance-New-Tenant-Max-Version", "ms-Exch-Sync-Service-Instance-New-Tenant-Min-Version", "ms-Exch-Transport-Dumpster-Hold-Time", "ms-Exch-Transport-Rule-Config", "ms-Exch-Virtual-Directory-Flags", "ms-Exch-Archive-Release", "ms-Exch-Mailbox-Release", "ms-Exch-Transport-Inbound-Protocol-Logging-Level", "ms-Exch-Configuration-XML", "ms-Exch-Component-States", "ms-Exch-On-Premises-Organization-Guid", "ms-Exch-Public-Folder-Deleted-Item-Retention", "ms-Exch-Smtp-TLS-Certificate", "ms-Exch-On-Premises-Inbound-Connector-Link", "ms-Exch-On-Premises-Inbound-Connector-BL", "ms-Exch-On-Premises-Outbound-Connector-Link", "ms-Exch-On-Premises-Outbound-Connector-BL", "ms-Exch-Auth-Next-Effective-Date", "ms-Exch-Organization-Upgrade-Request", "ms-Exch-Organization-Upgrade-Status", "ms-Exch-PolicyTip-Message-Config-Action", "ms-Exch-PolicyTip-Message-Config-Locale", "ms-Exch-PolicyTip-Message-Config-Message", "ms-Exch-Transport-Rule-ExpireTime", "ms-Exch-Transport-Rule-Version", "ms-Exch-Transport-Rule-Target-Link", "ms-Exch-Transport-Rule-Target-BL", "ms-Exch-Coexistence-Edge-Transport-Servers", "ms-Exch-Database-Group", "ms-Exch-Smtp-Tls-Senders-Certificate-Name", "ms-Exch-MDB-Availability-Group-Configuration-Link", "ms-Exch-MDB-Availability-Group-Configuration-BL", "ms-Exch-Calendar-Logging-Quota", "ms-Exch-Coexistence-Frontend-Transport-Servers", "ms-Exch-Monitoring-Override-Apply-Version", "ms-Exch-Previous-Recipient-Type-Details", "ms-Exch-MSO-Forward-Sync-Cookie-Property-Set-Version", "ms-Exch-MSO-Forward-Sync-Cookie-Timestamp", "ms-Exch-Associated-Accepted-Domain-Link", "ms-Exch-Associated-Accepted-Domain-BL", "ms-Exch-Catch-All-Recipient-Link", "ms-Exch-Catch-All-Recipient-BL", "ms-Exch-Public-Folder-Moved-Item-Retention", "ms-Exch-Push-Notifications-Throttling-Policy-State-Ex", "ms-Exch-Max-ABP", "ms-Exch-Max-OAB", "ms-Exch-Offline-OrgId-Home-Realm-Record", "ms-Exch-Provisioning-Tags", "ms-Exch-EvictedMembers-Link", "ms-Exch-EvictedMemebers-BL", "ms-Exch-Tenant-Country", "ms-Exch-Encryption-Throttling-Policy-State-Ex", "ms-Exch-Mailbox-Container-Guid", "ms-Exch-Unified-Mailbox", "ms-Exch-OAB-Generating-Mailbox-Link", "ms-Exch-OAB-Generating-Mailbox-BL", "ms-Exch-UG-Member-Link", "ms-Exch-UG-Member-BL", "ms-Exch-Aux-Mailbox-Parent-Object-Id-Link", "ms-Exch-Aux-Mailbox-Parent-Object-Id-BL", "ms-Exch-Multi-Mailbox-GUIDs", "ms-Exch-Sts-Refresh-Tokens-Valid-From", "ms-Exch-Multi-Mailbox-Locations-Link", "ms-Exch-Group-Security-Flags", "ms-Exch-Multi-Mailbox-Locations-BL", "ms-Exch-Multi-Mailbox-Databases-Link", "ms-Exch-Multi-Mailbox-Databases-BL", "ms-Exch-Auth-Policy-Link", "ms-Exch-Auth-Policy-BL", "ms-Exch-Administrative-Unit-Link", "ms-Exch-Administrative-Unit-BL", "ms-Exch-Immutable-Sid", "ms-Exch-UG-Event-Subscription-Link", "ms-Exch-UG-Event-Subscription-BL" + }, + [PSCustomObject]@{ + Name = "Exchange-Personal-Information" + RightsGuid = [Guid]::Parse("b1b3a417-ec55-4191-b327-b72e33e38af2") + MemberAttributes = "ms-Exch-UM-Pin-Checksum", "ms-Exch-Message-Hygiene-Flags", "ms-Exch-ELC-Mailbox-Flags", "ms-Exch-Message-Hygiene-SCL-Delete-Threshold", "ms-Exch-Message-Hygiene-SCL-Quarantine-Threshold", "ms-Exch-Message-Hygiene-SCL-Reject-Threshold", "ms-Exch-Safe-Recipients-Hash", "ms-Exch-Safe-Senders-Hash", "ms-Exch-Blocked-Senders-Hash", "ms-Exch-Device-Friendly-Name", "ms-Exch-Device-Health", "ms-Exch-Device-ID", "ms-Exch-Device-IMEI", "ms-Exch-Device-Mobile-Operator", "ms-Exch-Device-OS", "ms-Exch-Device-OS-Language", "ms-Exch-Device-Telephone-Number", "ms-Exch-Device-Type", "ms-Exch-Device-User-Agent", "ms-Exch-First-Sync-Time", "ms-Exch-Last-Update-Time", "ms-Exch-Signup-Addresses", "ms-Exch-User-Display-Name", "ms-Exch-Immutable-Id", "ms-Exch-Sharing-Partner-Identities", "ms-Exch-Transport-Recipient-Settings-Flags", "ms-Exch-External-Sync-State", "ms-Exch-Device-Model", "ms-Exch-UM-Calling-Line-IDs", "ms-Exch-Aggregation-Subscription-Credential", "ms-Exch-Send-As-Addresses", "ms-Exch-Retention-Comment", "ms-Exch-Retention-URL", "ms-Exch-Server-Association-Link", "ms-Exch-Server-Association-BL", "ms-Exch-Alternate-Mailboxes", "ms-Exch-Sharing-Policy-Link", "ms-Exch-UM-Addresses", "ms-Exch-UM-Phone-Provider", "ms-Exch-Supervision-User-Link", "ms-Exch-Supervision-DL-Link", "ms-Exch-Supervision-One-Off-Link", "ms-Exch-Archive-GUID", "ms-Exch-Archive-Name", "ms-Exch-Archive-Quota", "ms-Exch-Archive-Warn-Quota", "ms-Exch-OWA-Remote-Documents-Internal-Domain-Suffix-List-BL", "ms-Exch-Parent-Plan-BL", "ms-Exch-Supervision-DL-BL", "ms-Exch-Supervision-One-Off-BL", "ms-Exch-Archive-Database-Link", "ms-Exch-Archive-Database-BL", "ms-Exch-Device-Access-State", "ms-Exch-Device-Access-State-Reason", "ms-Exch-Device-EAS-Version", "ms-Exch-Mobile-Blocked-Device-IDs", "ms-Exch-Delegate-List-Link", "ms-Exch-Delegate-List-BL", "ms-Exch-Device-Access-Control-Rule-Link", "ms-Exch-Device-Access-Control-Rule-BL", "ms-Exch-Sharing-Anonymous-Identities", "ms-Exch-Litigation-Hold-Date", "ms-Exch-Litigation-Hold-Owner", "ms-Exch-Disabled-Archive-GUID", "ms-Exch-Disabled-Archive-Database-Link", "ms-Exch-Shadow-When-Soft-Deleted-Time" + }, + [PSCustomObject]@{ + Name = "Personal-Information" + RightsGuid = [Guid]::Parse("77b5b886-944a-11d1-aebd-0000f80367c1") + MemberAttributes = "Address", "Address-Home", "Assistant", "Comment", "Country-Name", "Facsimile-Telephone-Number", "International-ISDN-Number", "Locality-Name", "ms-DS-Host-Service-Account", "ms-DS-Supported-Encryption-Types", "ms-DS-Last-Successful-Interactive-Logon-Time", "ms-DS-Last-Failed-Interactive-Logon-Time", "ms-DS-Failed-Interactive-Logon-Count", "ms-DS-Failed-Interactive-Logon-Count-At-Last-Successful-Logon", "MSMQ-Digests", "MSMQ-Sign-Certificates", "Personal-Title", "Phone-Fax-Other", "Phone-Home-Other", "Phone-Home-Primary", "Phone-Ip-Other", "Phone-Ip-Primary", "Phone-ISDN-Primary", "Phone-Mobile-Other", "Phone-Mobile-Primary", "Phone-Office-Other", "Phone-Pager-Other", "Phone-Pager-Primary", "Physical-Delivery-Office-Name", "Picture", "Post-Office-Box", "Postal-Address", "Postal-Code", "Preferred-Delivery-Method", "Registered-Address", "State-Or-Province-Name", "Street-Address", "Telephone-Number", "Teletex-Terminal-Identifier", "Telex-Number", "Telex-Primary", "User-Cert", "User-Shared-Folder", "User-Shared-Folder-Other", "User-SMIME-Certificate", "X121-Address", "X509-Cert", "ms-DS-GeoCoordinates-Altitude", "ms-DS-GeoCoordinates-Latitude", "ms-DS-GeoCoordinates-Longitude", "ms-DS-cloudExtensionAttribute1", "ms-DS-cloudExtensionAttribute2", "ms-DS-cloudExtensionAttribute3", "ms-DS-cloudExtensionAttribute4", "ms-DS-cloudExtensionAttribute5", "ms-DS-cloudExtensionAttribute6", "ms-DS-cloudExtensionAttribute7", "ms-DS-cloudExtensionAttribute8", "ms-DS-cloudExtensionAttribute9", "ms-DS-cloudExtensionAttribute10", "ms-DS-cloudExtensionAttribute11", "ms-DS-cloudExtensionAttribute12", "ms-DS-cloudExtensionAttribute13", "ms-DS-cloudExtensionAttribute14", "ms-DS-cloudExtensionAttribute15", "ms-DS-cloudExtensionAttribute16", "ms-DS-cloudExtensionAttribute17", "ms-DS-cloudExtensionAttribute18", "ms-DS-cloudExtensionAttribute19", "ms-DS-cloudExtensionAttribute20", "ms-DS-External-Directory-Object-Id", "ms-Exch-Public-Delegates" + }, + [PSCustomObject]@{ + Name = "Public-Information" + RightsGuid = [Guid]::Parse("e48d0154-bcf8-11d1-8702-00c04fb96050") + MemberAttributes = "Additional-Information", "Allowed-Attributes", "Allowed-Attributes-Effective", "Allowed-Child-Classes", "Allowed-Child-Classes-Effective", "Alt-Security-Identities", "Common-Name", "Company", "Department", "Description", "Display-Name-Printable", "Division", "E-mail-Addresses", "Given-Name", "Initials", "Legacy-Exchange-DN", "Manager", "ms-DS-Allowed-To-Delegate-To", "ms-DS-Auxiliary-Classes", "ms-DS-Approx-Immed-Subordinates", "ms-DS-Phonetic-First-Name", "ms-DS-Phonetic-Last-Name", "ms-DS-Phonetic-Department", "ms-DS-Phonetic-Company-Name", "ms-DS-Phonetic-Display-Name", "ms-DS-HAB-Seniority-Index", "ms-DS-Source-Object-DN", "Obj-Dist-Name", "Object-Category", "Object-Class", "Object-Guid", "Organization-Name", "Organizational-Unit-Name", "Other-Mailbox", "Proxy-Addresses", "RDN", "Reports", "Service-Principal-Name", "Show-In-Address-Book", "Surname", "System-Flags", "Text-Country", "Text-Encoded-OR-Address", "Title", "User-Principal-Name", "ms-Exch-UC-Voice-Mail-Settings", "ms-Exch-User-Hold-Policies" + } ) + # cSpell:enable + $propertySetInfo +} diff --git a/Admin/Test-ExchangePropertyPermissions/Test-ExchangePropertyPermissions.ps1 b/Admin/Test-ExchangePropertyPermissions/Test-ExchangePropertyPermissions.ps1 new file mode 100644 index 0000000000..349ffd42c3 --- /dev/null +++ b/Admin/Test-ExchangePropertyPermissions/Test-ExchangePropertyPermissions.ps1 @@ -0,0 +1,232 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true, Position = 0)] + [string] + $TargetObjectDN, + + [Parameter(Mandatory = $true, Position = 1)] + [string] + $ComputerAccountDN, + + [Parameter(Mandatory = $false, Position = 2)] + [string] + $DomainController, + + [Parameter(Mandatory = $false, Position = 3)] + [switch] + $SaveReport, + + [Parameter(Mandatory = $false, Position = 3)] + [switch] + $OutputDebugInfo +) + +begin { + . $PSScriptRoot\..\..\Shared\Out-Columns.ps1 + . $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ActiveDirectoryAcl.ps1 + . $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ExchangeADSplitPermissionsEnabled.ps1 + . $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ExchangeOtherWellKnownObjects.ps1 + . $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ObjectTypeDisplayName.ps1 + . $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-TokenGroupsGlobalAndUniversal.ps1 + . $PSScriptRoot\Get-PropertySetInfo.ps1 + . $PSScriptRoot\Test-ExchangeSchema.ps1 + + $requiredWellKnownGroupsInToken = "Exchange Trusted Subsystem", "Exchange Servers" + + $report = [PSCustomObject]@{ + TargetObjectDN = $TargetObjectDN + ComputerAccountDN = $ComputerAccountDN + DomainController = $DomainController + RequiredInToken = @() + Token = $null + ACL = $null + ProblemsFound = @() + } +} + +process { + if (-not (Test-ExchangeSchema)) { + Write-Warning "Schema validation failed. Exiting." + return + } + + if (Get-ExchangeADSplitPermissionsEnabled) { + Write-Host "Split permissions is enabled. In this scenario, it is expected that the Exchange server + computer account does not have write permission to many recipient attributes. The script will + report these as problems, although they may be normal for this configuration." + } + + $token = Get-TokenGroupsGlobalAndUniversal -DistinguishedName $ComputerAccountDN + $report.Token = $token + Write-Host "Token groups: $ComputerAccountDN" + $token | Out-Columns + + $wellKnownObjects = Get-ExchangeOtherWellKnownObjects + foreach ($wellKnownName in $requiredWellKnownGroupsInToken) { + $groupDN = ($wellKnownObjects | Where-Object { $_.WellKnownName -eq $wellKnownName }).DistinguishedName + $objectSidBytes = ([ADSI]("LDAP://$groupDN")).Properties["objectSID"][0] + $objectSid = New-Object System.Security.Principal.SecurityIdentifier($objectSidBytes, 0) + $report.RequiredInToken += [PSCustomObject]@{ + WellKnownName = $wellKnownName + DistinguishedName = $groupDN + ObjectSid = $objectSid.ToString() + } + + $matchFound = $token | Where-Object { $_.SID -eq $objectSid.ToString() } + if ($null -eq $matchFound) { + $report.ProblemsFound += "The group $wellKnownName is not in the token." + } + } + + $params = @{ + DistinguishedName = $TargetObjectDN + } + + if (-not [string]::IsNullOrEmpty($DomainController)) { + $params.DomainController = $DomainController + } + + $acl = Get-ActiveDirectoryAcl @params + $objectTypeCache = @{} + $displayAces = @() + for ($i = 0; $i -lt $acl.Access.Count; $i++) { + $ace = $acl.Access[$i] + if ($ace.ObjectType -ne [Guid]::Empty) { + if ($null -ne $objectTypeCache[$ace.ObjectType]) { + $ace | Add-Member -NotePropertyName ObjectTypeDisplay -NotePropertyValue $objectTypeCache[$ace.ObjectType] + } else { + $objectTypeDisplay = Get-ObjectTypeDisplayName -ObjectType $ace.ObjectType + $objectTypeCache[$ace.ObjectType] = $objectTypeDisplay + $ace | Add-Member -NotePropertyName ObjectTypeDisplay -NotePropertyValue $objectTypeDisplay + } + } + + if ($ace.InheritedObjectType -ne [Guid]::Empty) { + if ($null -ne $objectTypeCache[$ace.InheritedObjectType]) { + $ace | Add-Member -NotePropertyName InheritedObjectTypeDisplay -NotePropertyValue $objectTypeCache[$ace.InheritedObjectType] + } else { + $objectTypeDisplay = Get-ObjectTypeDisplayName -ObjectType $ace.InheritedObjectType + $objectTypeCache[$ace.InheritedObjectType] = $objectTypeDisplay + $ace | Add-Member -NotePropertyName InheritedObjectTypeDisplay -NotePropertyValue $objectTypeDisplay + } + } + + $ace | Add-Member -MemberType NoteProperty -Name "Index" -Value $i + $displayAces += $ace + } + + $report.ACL = $displayAces + Write-Host "ACL: $TargetObjectDN" + $displayAces | Where-Object { $_.PropagationFlags -ne "InheritOnly" } | Out-Columns -Properties Index, IdentityReference, AccessControlType, ActiveDirectoryRights, ObjectTypeDisplay, IsInherited + + $propertySetInfo = Get-PropertySetInfo + $attributeCount = $propertySetInfo.MemberAttributes.Count + $progressCount = 0 + $sw = New-Object System.Diagnostics.Stopwatch + $sw.Start() + $schemaPath = ([ADSI]("LDAP://$([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)/RootDSE")).Properties["schemaNamingContext"][0] + $identityReferenceCache = @{} + foreach ($propertySet in $propertySetInfo) { + foreach ($attributeName in $propertySet.MemberAttributes) { + $progressCount++ + if ($sw.ElapsedMilliseconds -gt 1000) { + $sw.Restart() + Write-Progress -Activity "Checking permissions" -PercentComplete $((($progressCount * 100) / $attributeCount)) + } + + $attributeSchemaEntry = [ADSI]("LDAP://CN=$attributeName,$schemaPath") + if ($attributeSchemaEntry.Properties["attributeSecurityGuid"].Count -lt 1) { + # This schema validation failure should be extremely rare, but we have seen a few + # cases in lab/dev/test environments, such as when ADSchemaAnalyzer has been used to + # copy schema between forests. + $report.ProblemsFound += "The attribute $attributeName is not in the $($propertySet.Name) property set." + continue + } + + $schemaIdGuid = New-Object Guid(, $attributeSchemaEntry.Properties["schemaIDGuid"][0]) + + # We need to hit a write allow ACE for a SID in the token on one of the following: + # - The rightsGuid from the property set + # - The schemaIdGuid from the attributeSchemaEntry + # We must hit the allow before we hit a deny on the same thing. + + $found = $false + $problemAceIndex = $null + for ($i = 0; $i -lt $displayAces.Count; $i++) { + $ace = $displayAces[$i] + if ($ace.PropagationFlags -eq "InheritOnly") { + continue + } + + $sidToFind = $null + if ($null -eq $ace.IdentityReference.SID) { + if ($null -ne $identityReferenceCache[$ace.IdentityReference.Value]) { + $sidToFind = $identityReferenceCache[$ace.IdentityReference.Value] + } else { + $sidToFind = $ace.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value + $identityReferenceCache[$ace.IdentityReference.Value] = $sidToFind + } + } else { + $sidToFind = $ace.IdentityReference.SID + } + + $matchingSid = $token | Where-Object { $_.SID -eq $sidToFind.ToString() } + if ($null -ne $matchingSid) { + # The ACE affects this token. + # Does it affect this property? + if ($ace.ObjectType -eq $propertySet.RightsGuid -or $ace.ObjectType -eq $schemaIdGuid -or $ace.ObjectType -eq [Guid]::Empty) { + if ($ace.ActiveDirectoryRights -contains "WriteProperty" -or $ace.ActiveDirectoryRights -contains "GenericAll") { + if ($ace.AccessControlType -eq "Allow") { + $found = $true + break + } else { + $problemAceIndex = $i + break + } + } + } + } + } + + if (-not $found) { + if ($null -ne $problemAceIndex) { + $report.ProblemsFound += "The property $attributeName is denied Write by ACE $problemAceIndex." + } else { + $report.ProblemsFound += "The property $attributeName is not allowed Write by any ACE." + } + } + } + } + + if ($report.ProblemsFound.Count -gt 0) { + foreach ($problem in $report.ProblemsFound) { + Write-Warning $problem + } + } else { + Write-Host "No problems found." + } + + if ($SaveReport) { + $reportPath = $PSScriptRoot + "\" + "PermissionReport-$([DateTime]::Now.ToString("yyMMddHHmmss")).xml" + $report | Export-Clixml $reportPath + Write-Host "Report saved to $reportPath" + } + + if ($OutputDebugInfo) { + $debugInfo = @{ + ACL = $acl + DisplayAces = $displayAces + IdentityReferenceCache = $identityReferenceCache + Token = $token + TargetObjectDN = $TargetObjectDN + Report = $report + } + + $debugInfoPath = Join-Path $PSScriptRoot "DebugInfo.xml" + $debugInfo | Export-Clixml -Path $debugInfoPath + Write-Host "Debug info saved to $debugInfoPath" + } +} diff --git a/Admin/Test-ExchangePropertyPermissions/Test-ExchangeSchema.ps1 b/Admin/Test-ExchangePropertyPermissions/Test-ExchangeSchema.ps1 new file mode 100644 index 0000000000..6fd78b9487 --- /dev/null +++ b/Admin/Test-ExchangePropertyPermissions/Test-ExchangeSchema.ps1 @@ -0,0 +1,80 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function Test-ExchangeSchema { + [CmdletBinding()] + + # cSpell:disable + $requiredSchemaEntries = @( + [PSCustomObject]@{ SchemaObject = "User"; AttributeName = "systemAuxiliaryClass"; RequiredValues = @("mailRecipient") }, + [PSCustomObject]@{ SchemaObject = "Group"; AttributeName = "systemAuxiliaryClass"; RequiredValues = @("mailRecipient") }, + [PSCustomObject]@{ SchemaObject = "mail-Recipient"; AttributeName = "mayContain"; RequiredValues = + @("altRecipient", "altRecipientBL", "assistant", "authOrig", "authOrigBL", "autoReplyMessage", + "company", "delivContLength", "deliverAndRedirect", "deliveryMechanism", "delivExtContTypes", "department", + "dLMemDefault", "dLMemRejectPerms", "dLMemRejectPermsBL", "dLMemSubmitPerms", "dLMemSubmitPermsBL", "dnQualifier", + "enabledProtocols", "expirationTime", "extensionData", "folderPathname", "formData", "forwardingAddress", + "homeMTA", "importedFrom", "internetEncoding", "labeledURI", "language", "languageCode", "mail", "mailNickname", + "mAPIRecipient", "msDS-ExternalDirectoryObjectId", "msDS-GeoCoordinatesAltitude", "msDS-GeoCoordinatesLatitude", + "msDS-GeoCoordinatesLongitude", "msDS-HABSeniorityIndex", "msDS-PhoneticDisplayName", "msExchAddressBookFlags", + "msExchAdministrativeUnitLink", "msExchAggregationSubscriptionCredential", "msExchArbitrationMailbox", + "msExchArchiveRelease", "msExchAssistantName", "msExchAuditAdmin", "msExchAuditDelegate", "msExchAuditDelegateAdmin", + "msExchAuditOwner", "msExchAuthPolicyLink", "msExchAuxMailboxParentObjectIdLink", "msExchBlockedSendersHash", + "msExchBypassAudit", "msExchBypassModerationBL", "msExchBypassModerationFromDLMembersBL", + "msExchBypassModerationFromDLMembersLink", "msExchBypassModerationLink", "msExchCalculatedTargetAddress", + "msExchCalendarRepairDisabled", "msExchCapabilityIdentifiers", "msExchCoManagedObjectsBL", "msExchConfigurationXML", + "msExchCustomProxyAddresses", "msExchDirsyncID", "msExchDirsyncSourceObjectClass", "msExchEdgeSyncRetryCount", + "msExchEnableModeration", "msExchEwsApplicationAccessPolicy", "msExchEwsEnabled", "msExchEwsExceptions", + "msExchEwsWellKnownApplicationPolicies", "msExchExpansionServerName", "msExchExternalSyncState", "msExchFBURL", + "msExchForeignGroupSID", "msExchGenericForwardingAddress", "msExchGroupExternalMemberCount", "msExchGroupMemberCount", + "msExchGroupSecurityFlags", "msExchHABShowInDepartments", "msExchHomeMTASL", "msExchImmutableId", "msExchImmutableSid", + "msExchIntendedMailboxPlanLink", "msExchInterruptUserOnAuditFailure", "msExchLabeledURI", "msExchLicenseToken", + "msExchLitigationHoldDate", "msExchLitigationHoldOwner", "msExchLocalizationFlags", "msExchMailboxAuditEnable", + "msExchMailboxAuditLastAdminAccess", "msExchMailboxAuditLastDelegateAccess", "msExchMailboxAuditLastExternalAccess", + "msExchMailboxAuditLogAgeLimit", "msExchMailboxFolderSet", "msExchMailboxFolderSet2", "msExchMailboxMoveBatchName", + "msExchMailboxMoveFlags", "msExchMailboxMoveRemoteHostName", "msExchMailboxMoveSourceArchiveMDBLink", + "msExchMailboxMoveSourceArchiveMDBLinkSL", "msExchMailboxMoveSourceMDBLink", "msExchMailboxMoveSourceMDBLinkSL", + "msExchMailboxMoveStatus", "msExchMailboxMoveTargetArchiveMDBLink", "msExchMailboxMoveTargetArchiveMDBLinkSL", + "msExchMailboxMoveTargetMDBLink", "msExchMailboxMoveTargetMDBLinkSL", "msExchMailboxPlanType", "msExchMailboxRelease", + "msExchMailboxSecurityDescriptor", "msExchMasterAccountSid", "msExchMessageHygieneFlags", + "msExchMessageHygieneSCLDeleteThreshold", "msExchMessageHygieneSCLJunkThreshold", + "msExchMessageHygieneSCLQuarantineThreshold", "msExchMessageHygieneSCLRejectThreshold", "msExchModeratedByLink", + "msExchModeratedObjectsBL", "msExchModerationFlags", "msExchMultiMailboxDatabasesLink", "msExchObjectID", + "msExchOrganizationUpgradeRequest", "msExchOrganizationUpgradeStatus", "msExchOWAPolicy", "msExchParentPlanLink", + "msExchPartnerGroupID", "msExchPoliciesExcluded", "msExchPoliciesIncluded", "msExchPolicyEnabled", + "msExchPolicyOptionList", "msExchPreviousAccountSid", "msExchPreviousRecipientTypeDetails", "msExchProvisioningFlags", + "msExchProxyCustomProxy", "msExchPublicFolderMailbox", "msExchPublicFolderSmtpAddress", "msExchRBACPolicyLink", + "msExchRecipientDisplayType", "msExchRecipientSoftDeletedStatus", "msExchRecipientTypeDetails", "msExchRecipLimit", + "msExchRemoteRecipientType", "msExchRequireAuthToSendTo", "msExchResourceCapacity", "msExchResourceDisplay", + "msExchResourceMetaData", "msExchResourceSearchProperties", "msExchRetentionComment", "msExchRetentionURL", + "msExchRMSComputerAccountsLink", "msExchRoleGroupType", "msExchSafeRecipientsHash", "msExchSafeSendersHash", + "msExchSendAsAddresses", "msExchSenderHintTranslations", "msExchShadowWhenSoftDeletedTime", + "msExchSharingAnonymousIdentities", "msExchSharingPartnerIdentities", "msExchSharingPolicyLink", "msExchSignupAddresses", + "msExchStsRefreshTokensValidFrom", "msExchSupervisionDLLink", "msExchSupervisionOneOffLink", "msExchSupervisionUserLink", + "msExchSyncAccountsPolicyDN", "msExchTextMessagingState", "msExchThrottlingPolicyDN", + "msExchTransportRecipientSettingsFlags", "msExchUCVoiceMailSettings", "msExchUGEventSubscriptionLink", "msExchUGMemberLink", + "msExchUMAddresses", "msExchUMCallingLineIDs", "msExchUMDtmfMap", "msExchUMListInDirectorySearch", + "msExchUMRecipientDialPlanLink", "msExchUMSpokenName", "msExchUsageLocation", "msExchUserAccountControl", + "msExchUserHoldPolicies", "msExchWhenMailboxCreated", "msExchWhenSoftDeletedTime", "msExchWindowsLiveID", "pOPCharacterSet", + "pOPContentFormat", "protocolSettings", "publicDelegates", "publicDelegatesBL", "replicationSensitivity", "secretary", + "securityProtocol", "submissionContLength", "targetAddress", "unauthOrig", "unauthOrigBL", "userSMIMECertificate", + "versionNumber") + } + ) + # cSpell:enable + + $schemaPath = ([ADSI]("LDAP://$([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)/RootDSE")).Properties["schemaNamingContext"][0] + + $schemaIsGood = $true + + foreach ($o in $requiredSchemaEntries) { + $schemaObject = [ADSI]("LDAP://CN=$($o.SchemaObject),$schemaPath") + $attributeValues = $schemaObject.Properties[$o.AttributeName] + $missingValues = $o.RequiredValues | Where-Object { $attributeValues -notcontains $_ } + if ($missingValues) { + Write-Host "$($o.SchemaObject) missing $($o.AttributeName): $missingValues" + $schemaIsGood = $false + } + } + + return $schemaIsGood +} diff --git a/Shared/ActiveDirectoryFunctions/Get-ObjectTypeDisplayName.ps1 b/Shared/ActiveDirectoryFunctions/Get-ObjectTypeDisplayName.ps1 new file mode 100644 index 0000000000..7371a95ff6 --- /dev/null +++ b/Shared/ActiveDirectoryFunctions/Get-ObjectTypeDisplayName.ps1 @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +function Get-ObjectTypeDisplayName { + [CmdletBinding()] + param ( + [Parameter()] + [Guid] + $ObjectType + ) + + $rootDSE = [ADSI]"LDAP://$([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)/RootDSE" + $extendedRightsContainer = [ADSI]"LDAP://$("CN=Extended-Rights," + $rootDSE.ConfigurationNamingContext)" + $searcher = New-Object System.DirectoryServices.DirectorySearcher($extendedRightsContainer, "(&(rightsGuid=$ObjectType))", "displayName") + $result = $searcher.FindOne() + + if ($null -ne $result) { + $result.Properties["displayName"][0] + return + } + + $schemaContainer = [ADSI]"LDAP://$("CN=Schema," + $rootDSE.ConfigurationNamingContext)" + $objectTypeBytes = [string]::Join("", ($ObjectType.ToByteArray() | ForEach-Object { ("\" + $_.ToString("X")) })) + $searcher = New-Object System.DirectoryServices.DirectorySearcher($schemaContainer, "(&(schemaIdGuid=$objectTypeBytes))", "lDAPDisplayName") + $result = $searcher.FindOne() + if ($null -ne $result) { + $result.Properties["lDAPDisplayName"][0] + return + } + + throw "ObjectType $ObjectType not found" +} diff --git a/docs/Admin/Test-ExchangePropertyPermissions.md b/docs/Admin/Test-ExchangePropertyPermissions.md new file mode 100644 index 0000000000..338a15b6d8 --- /dev/null +++ b/docs/Admin/Test-ExchangePropertyPermissions.md @@ -0,0 +1,41 @@ +# Test-ExchangePropertyPermissions + +Download the latest release: [Update-Engines.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Update-Engines.ps1) + +## Syntax + +```powershell +Test-ExchangePropertyPermissions.ps1 + [-TargetObjectDN] + [-ComputerAccountDN] + [[-DomainController] ] + [-OutputDebugInfo] + [] +``` + +## Example + +.\Test-ExchangePropertyPermissions.ps1 -TargetObjectDN "CN=SomeRecipient,OU=Users,DC=contoso,DC=com" -ComputerAccountDN "CN=SomeServerName,OU=Computers,DC=contoso,DC=com" + +This example retrieves the group memberships of the SomeServerName computer account and then examines the ACL of SomeRecipient +to determine if that computer account can write to all expected attributes of that recipient. + +## Description + +Test-ExchangePropertyPermissions is designed to detect certain schema issues which can manifest as +permissions problems and can be challenging to identify manually, including: + +* Scenarios where a property set does not include all the expected properties. +* Scenarios where an objectClass definition is missing expected properties. + +Note that the script does not perform an exhaustive check for all possible schema issues. It is +only designed to identify a specific subset of issues which we have encountered. For example, using +AD Schema Analyzer as described here is one such scenario: + +https://learn.microsoft.com/en-us/previous-versions/technet-magazine/dd547839(v=msdn.10) + +As noted in that article, this is known to corrupt the Exchange attributes. This script is able +to detect that scenario, and other similar scenarios. + +Further, note that such issues cannot be fixed by the script. Using AD Schema Analyzer as described +results in an unsupported forest that should be torn down. diff --git a/mkdocs.yml b/mkdocs.yml index c4dd8d0f1e..69eac08311 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ nav: - Reset-ScanEngineVersion: Admin/Reset-ScanEngineVersion.md - SetUnifiedContentPath: Admin/SetUnifiedContentPath.md - Test-AMSI: Admin/Test-AMSI.md + - Test-ExchangePropertyPermissions: Admin/Test-ExchangePropertyPermissions.md - Update-Engines: Admin/Update-Engines.md - Calendar: - Check-SharingStatus: Calendar/Check-SharingStatus.md From deb8ca326fb22bbc1aa5886183ec09bae96ce31e Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 21 Dec 2023 07:39:02 -0600 Subject: [PATCH 13/15] Fix bug with NULL authLocation on unknown site location --- .../Analyzer/Get-IISAuthenticationType.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 b/Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 index db9ddf6785..b735855d17 100644 --- a/Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Get-IISAuthenticationType.ps1 @@ -187,6 +187,15 @@ function Get-IISAuthenticationType { } } else { Write-Verbose "authLocation was NULL, but shouldn't be a problem we just use the parent." + $index = $currentKey.LastIndexOf("/") + + if ($index -eq -1) { + $continue = $false + Write-Verbose "No parent location. Need to determine how to address." + $getIisAuthenticationType[$failedKey].Add($appKey) + } else { + $currentKey = $currentKey.Substring(0, $index) + } } } elseif ($currentKey -ne $appKey) { # If we are at a parent location we might not have all the locations in the config. So this could be okay. @@ -196,6 +205,7 @@ function Get-IISAuthenticationType { if ($index -eq -1) { Write-Verbose "Didn't have root parent in the config file, this is odd." $getIisAuthenticationType[$failedKey].Add($appKey) + $continue = $false } else { $currentKey = $currentKey.Substring(0, $index) } From 8b93ec104e52ac7b937c3bfdbf43956a6258af27 Mon Sep 17 00:00:00 2001 From: Bhalchandra Atre-MSFT <39634045+Batre-MSFT@users.noreply.github.com> Date: Fri, 22 Dec 2023 16:58:46 +0530 Subject: [PATCH 14/15] Update Emerging-Issues.md Update PF permission issue after enabling EP --- docs/Emerging-Issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Emerging-Issues.md b/docs/Emerging-Issues.md index 9a2885cf5d..a3fa35829a 100644 --- a/docs/Emerging-Issues.md +++ b/docs/Emerging-Issues.md @@ -13,7 +13,7 @@ This page lists emerging issues for Exchange On-Premises deployments, possible r 10/12/2023|[All versions of August 2023 Security Update](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-august-2023-exchange-server-security-updates/ba-p/3892811) for Exchange 2016, Exchange 2019 | Users in account forest can't change expired password in OWA in multi-forest Exchange deployments after installing any version of [August 2023 Security Update for Exchange servers](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-august-2023-exchange-server-security-updates/ba-p/3892811)

**Note**
The account forest user will be able to change the password after they sign in to Outlook on the web if their password is not yet expired. The issue affects only account forest users who have passwords that are already expired. This change does not affect users in organizations that don't use multiple forests.|** Update on 10/12/2023 **

Follow steps on [this article](https://support.microsoft.com/topic/users-in-account-forest-can-t-change-expired-password-in-owa-in-multi-forest-exchange-deployments-after-installing-august-2023-su-b17c3579-0233-4d84-9245-755dd1092edb) 8/15/2023|[Non-English August 2023 Security Update](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-august-2023-exchange-server-security-updates/ba-p/3892811) for Exchange 2016, Exchange 2019 | When you install the Microsoft Exchange Server 2019 or 2016 August 2023 Security Update (SU) on a Windows Server-based device that is running a non-English operating system (OS) version, Setup suddenly stops and rolls back the changes. However, the Exchange Server services remain in a disabled state. |The latest SUs have been released that do not require a workaround to install. If you used a workaround to install KB5029388, it is highly recommend to uninstall the KB5029388 to avoid issues down the line. For more information please check out [this KB](https://support.microsoft.com/topic/exchange-server-2019-and-2016-august-2023-security-update-installation-fails-on-non-english-operating-systems-ef38d805-f645-4511-8cc5-cf967e5d5c75). 6/15/2023|[January 2023 Security Update](https://www.microsoft.com/en-us/download/details.aspx?id=104914) for Exchange 2016, Exchange 2019 | When you try to uninstall Microsoft Exchange Server 2019 or 2016 on servers, that had January 2023 Security Update for Exchange Server installed at any point, the Setup fails with following error message:

[ERROR] The operation couldn't be performed because object '' couldn't be found on ''. |Install Exchange Security Update June 2023 or higher to resolve the issue. Check [this KB](https://support.microsoft.com/help/5025312) for more details -6/15/2023|Extended protection enabled on Exchange server | Changing the permissions for Public Folders by using an Outlook client will fail with the following error, if Extended Protection is enabled:

`The modified Permissions cannot be changed.`| Install Exchange Security Update June 2023 or higher to resolve the issue. Check [this KB](https://support.microsoft.com/en-us/topic/extended-protection-doesn-t-support-public-folder-client-permissions-management-through-outlook-bd2037b5-40e0-413a-b368-746b3f5439ee) for more details +6/15/2023|Extended protection enabled on Exchange server | Changing the permissions for Public Folders by using an Outlook client will fail with the following error, if Extended Protection is enabled:

`The modified Permissions cannot be changed.`| Install Exchange Security Update June 2023 or higher Security Update and create the setting override mentioned in [this KB](https://support.microsoft.com/topic/extended-protection-doesn-t-support-public-folder-client-permissions-management-through-outlook-bd2037b5-40e0-413a-b368-746b3f5439ee) |3/16/2023| [Outlook client update for CVE-2023-23397 released](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-23397)| These vulnerabilities affect Exchange Server. Exchange Online customers are already protected from the vulnerabilities addressed in these SUs and do not need to take any action **other than updating Exchange servers in their environment, and if applicable, installing the security update for Outlook on Windows described on the link on the right.**
More details about specific CVEs can be found in the [Security Update Guide](https://msrc.microsoft.com/update-guide/) (filter on Exchange Server under Product Family).
**Awareness: Outlook client update for CVE-2023-23397 released**
There is a critical security update for Microsoft Outlook for Windows that is required to address [CVE-2023-23397](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-23397). To address this CVE, **you must install the Outlook security update, regardless of where your mail is hosted (e.g., Exchange Online, Exchange Server, some other platform).** | **Please check [this page](https://aka.ms/OLKCVEFAQ) for FAQs about the [Outlook CVE-2023-23397](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-23397)** 3/14/2023|[February 2023 Security Update](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-february-2023-exchange-server-security-updates/ba-p/3741058) for Exchange 2016, Exchange 2019, Exchange 2013 | After installing February 2023 security update, customers are seeing EWS application pool crash with Event ID 4999 with following error

E12IIS, c-RTL-AMD64, 15.01.2507.021, w3wp#MSExchangeServicesAppPool, M.Exchange.Diagnostics, M.E.D.ChainedSerializationBinder.EnforceBlockReason, M.E.Diagnostics.BlockedDeserializeTypeException, 437c-dumptidset, 15.01.2507.021.

The issue is causing connectivity issues to EWS based clients (Outlook for Mac) | **Update on 3/14/2023**
The issue is fixed in [March 2023 security update for Exchange servers](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-march-2023-exchange-server-security-updates/ba-p/3764224)
Please follow the steps in [this KB](https://support.microsoft.com/help/5024257) 3/14/2023|[February 2023 Security Update](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-february-2023-exchange-server-security-updates/ba-p/3741058) for Exchange 2016, Exchange 2019, Exchange 2013 | Some customers are reporting issues with Outlook/OWA add-ins, like add-in not listing in EAC or with the Get-App command. Additionally, they may notice EWS application pool crash with Event ID 4999 in the application log of the Exchange server. | **Update on 3/14/2023**
The issue is fixed in [March 2023 security update for Exchange servers](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-march-2023-exchange-server-security-updates/ba-p/3764224) From 3aeb429642a6d97634f5633b465eb47f75b46058 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 5 Jan 2024 12:21:22 -0600 Subject: [PATCH 15/15] Address bug with HSTS detection with no ; in value --- .../IISInformation/Get-IISWebSite.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/IISInformation/Get-IISWebSite.ps1 b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/IISInformation/Get-IISWebSite.ps1 index 6789e06794..48595c5edd 100644 --- a/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/IISInformation/Get-IISWebSite.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/ExchangeInformation/IISInformation/Get-IISWebSite.ps1 @@ -101,9 +101,13 @@ function Get-IISWebSite { # add 8 to find the start index after 'max-age=' $maxAgeIndex = $maxAgeIndex + 8 - # subtract maxAgeIndex to get the length that we need to find the substring - $maxAgeValueIndex = $maxAgeValueIndex - $maxAgeIndex - $customHeaderHstsObj.'max-age' = $customHeaderHsts.Substring($maxAgeIndex, $maxAgeValueIndex) + if ($maxAgeValueIndex -ne -1) { + # subtract maxAgeIndex to get the length that we need to find the substring + $maxAgeValueIndex = $maxAgeValueIndex - $maxAgeIndex + $customHeaderHstsObj.'max-age' = $customHeaderHsts.Substring($maxAgeIndex, $maxAgeValueIndex) + } else { + $customHeaderHstsObj.'max-age' = $customHeaderHsts.Substring($maxAgeIndex) + } } else { Write-Verbose "max-age directive not found" }