From 0b233afc943dcf21ce921d62574c9944fbb6136d Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 16 Nov 2023 17:09:20 -0600 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 72d3dbae4519d1488c9e559a09e94b05fd8275f0 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Mon, 18 Dec 2023 14:42:00 -0600 Subject: [PATCH 6/7] 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 f7b80afd96b7f271e15bbfad36840d19925723b2 Mon Sep 17 00:00:00 2001 From: Bill Long Date: Mon, 10 Oct 2022 15:39:08 -0500 Subject: [PATCH 7/7] 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