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/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..8118a81b07 --- /dev/null +++ b/Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 @@ -0,0 +1,116 @@ +# Copyright (c) Microsoft Corporation. +# 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. + 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-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..a6dfcd7286 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( @@ -28,9 +31,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 +89,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 +103,7 @@ function Invoke-AnalyzerIISInformation { $outputObjectDisplayValue.Add([PSCustomObject]@{ Name = $webSite.Name State = $webSite.State + "HSTS Enabled" = $hstsEnabled $bindingsPropertyName = $value }) } @@ -75,6 +121,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 +336,346 @@ function Invoke-AnalyzerIISInformation { Add-AnalyzedResultInformation @params } + ######################################## + # 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 + #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 } + $currentRewriteRules = $urlRewriteRules[$location.Path] + $authentication = $authTypeSettings[$location.Path] + + if ($currentRewriteRules.Count -ne 0) { + # 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) { + 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 -contains "ssl") $(if(($ssl -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 } + # 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 } + + if ($null -ne $siteConfigPaths) { + $missingWebApplicationConfigFile = $exchangeInformation.IISSettings.IISWebApplication | + 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. + $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() + $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) + Write-Verbose "Testing path: $testPath" + if (-not ($paths -contains $testPath)) { + return $_ + } + } + } + + # 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) { + + 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 + } + + #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" + 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" 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