From 738cdefff53afb9e237740c6772a81b373bd13eb Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 8 Jul 2026 16:30:25 -0500 Subject: [PATCH 1/3] Add July 2026 SU build numbers to Get-ExchangeBuildVersionInformation Add Jul26SU build entries for: - Exchange 2016 CU23: 15.1.2507.71 - Exchange 2019 CU14: 15.2.1544.43 - Exchange 2019 CU15: 15.2.1748.48 - Exchange SE RTM: 15.2.2562.45 Update latestSUBuild markers to Jul26SU for all supported versions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Shared/Get-ExchangeBuildVersionInformation.ps1 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Shared/Get-ExchangeBuildVersionInformation.ps1 b/Shared/Get-ExchangeBuildVersionInformation.ps1 index ef2cd8f17c..a1808ec472 100644 --- a/Shared/Get-ExchangeBuildVersionInformation.ps1 +++ b/Shared/Get-ExchangeBuildVersionInformation.ps1 @@ -131,7 +131,7 @@ function Get-ExchangeBuildVersionInformation { $cuReleaseDate = "07/01/2025" $supportedBuildNumber = $true } - (GetBuildVersion $exSE "RTM" -SU "Jun26SU") { $latestSUBuild = $true } + (GetBuildVersion $exSE "RTM" -SU "Jul26SU") { $latestSUBuild = $true } } } elseif ($exchangeVersion.Major -eq 15 -and $exchangeVersion.Minor -eq 2) { Write-Verbose "Exchange 2019 is detected" @@ -150,14 +150,14 @@ function Get-ExchangeBuildVersionInformation { $cuReleaseDate = "02/10/2025" $supportedBuildNumber = $true } - (GetBuildVersion $ex19 "CU15" -SU "Jun26SU") { $latestSUBuild = $true } + (GetBuildVersion $ex19 "CU15" -SU "Jul26SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex19 "CU15") } { $cuLevel = "CU14" $cuReleaseDate = "02/13/2024" $supportedBuildNumber = $true $orgValue = 16762 } - (GetBuildVersion $ex19 "CU14" -SU "Jun26SU") { $latestSUBuild = $true } + (GetBuildVersion $ex19 "CU14" -SU "Jul26SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex19 "CU14") } { $cuLevel = "CU13" $cuReleaseDate = "05/03/2023" @@ -254,7 +254,7 @@ function Get-ExchangeBuildVersionInformation { $cuReleaseDate = "04/20/2022" $supportedBuildNumber = $true } - (GetBuildVersion $ex16 "CU23" -SU "Jun26SU") { $latestSUBuild = $true } + (GetBuildVersion $ex16 "CU23" -SU "Jul26SU") { $latestSUBuild = $true } { $_ -lt (GetBuildVersion $ex16 "CU23") } { $cuLevel = "CU22" $cuReleaseDate = "09/28/2021" @@ -750,6 +750,7 @@ function GetExchangeBuildDictionary { "Dec25SU" = "15.1.2507.63" "Feb26SU" = "15.1.2507.66" "Jun26SU" = "15.1.2507.69" + "Jul26SU" = "15.1.2507.71" }) } "Exchange2019" = @{ @@ -865,6 +866,7 @@ function GetExchangeBuildDictionary { "Dec25SU" = "15.2.1544.37" "Feb26SU" = "15.2.1544.39" "Jun26SU" = "15.2.1544.41" + "Jul26SU" = "15.2.1544.43" }) "CU15" = (NewCUAndSUObject "15.2.1748.10" @{ "Apr25HU" = "15.2.1748.24" @@ -875,6 +877,7 @@ function GetExchangeBuildDictionary { "Dec25SU" = "15.2.1748.42" "Feb26SU" = "15.2.1748.43" "Jun26SU" = "15.2.1748.46" + "Jul26SU" = "15.2.1748.48" }) } "ExchangeSE" = @{ @@ -886,6 +889,7 @@ function GetExchangeBuildDictionary { "Feb26SU" = "15.2.2562.37" "May26HU" = "15.2.2562.41" "Jun26SU" = "15.2.2562.43" + "Jul26SU" = "15.2.2562.45" }) } } From b7292b487d18cf8b8186b7e85af5540bad7f4d5b Mon Sep 17 00:00:00 2001 From: David Paulson Date: Wed, 8 Jul 2026 16:30:31 -0500 Subject: [PATCH 2/3] Add July 2026 SU CVEs to HealthChecker security check Add Jul26SU entry with 4 CVEs for Exchange 2016, 2019, and SE: CVE-2026-55005, CVE-2026-55006, CVE-2026-55008, CVE-2026-55009 Update SE test CVE count from 19 to 23 accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 | 1 + Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 index 97828d366a..fcd4b19645 100644 --- a/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1 @@ -145,6 +145,7 @@ function Invoke-AnalyzerSecurityCveCheck { "Dec25SU" = (NewCveEntry @("CVE-2025-64666", "CVE-2025-64667") @($ex2016, $ex2019, $exSE)) "Feb26SU" = (NewCveEntry @("CVE-2026-21527") @($ex2016, $ex2019, $exSE)) "Jun26SU" = (NewCveEntry @("CVE-2026-42897", "CVE-2026-45500", "CVE-2026-45501", "CVE-2026-45502", "CVE-2026-45503", "CVE-2026-45504", "CVE-2026-47631") @($ex2016, $ex2019, $exSE)) + "Jul26SU" = (NewCveEntry @("CVE-2026-55005", "CVE-2026-55006", "CVE-2026-55008", "CVE-2026-55009") @($ex2016, $ex2019, $exSE)) } # Need to organize the list so oldest CVEs come out first. diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 index 21cd5913c1..d175717b33 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 @@ -197,7 +197,7 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange SE" { SetActiveDisplayGrouping "Security Vulnerability" $cveTests = GetObject "Security Vulnerability" - $cveTests.Count | Should -Be 19 + $cveTests.Count | Should -Be 23 $downloadDomains = GetObject "CVE-2021-1730" $downloadDomains.DownloadDomainsEnabled | Should -Be "False" From e758b7ed39a6d8b2ae306c332fb0dd3eee5b2968 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 10 Jul 2026 13:06:51 -0500 Subject: [PATCH 3/3] Add Health Checker check for legacy Exchange security groups Detect legacy Exchange security groups (Exchange Domain Servers, Exchange Enterprise Servers, Exchange Recipient Administrators) in Active Directory via a single Global Catalog query using the shared Search-AllActiveDirectoryDomains helper, and surface a Yellow best-practice warning in the Organization Information section recommending they be reviewed and removed. Results are rendered as an Out-Columns table (DistinguishedName, Scope, Members). Pester coverage mocks Search-AllActiveDirectoryDomains so the real parsing logic is exercised. Resolves #2559 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1717cb36-0cf5-4217-a37c-c97f7dc04bd5 --- ...Invoke-AnalyzerOrganizationInformation.ps1 | 62 ++++++++++++++++ .../Get-ExchangeLegacySecurityGroups.ps1 | 72 +++++++++++++++++++ .../Invoke-JobOrganizationInformation.ps1 | 4 ++ .../Tests/HealthChecker.SE.Main.Tests.ps1 | 1 + .../HealthChecker.SE.Scenarios.Tests.ps1 | 35 +++++++++ ...thCheckerTest.CommonMocks.NotPublished.ps1 | 33 +++++++++ 6 files changed, 207 insertions(+) create mode 100644 Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Get-ExchangeLegacySecurityGroups.ps1 diff --git a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOrganizationInformation.ps1 b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOrganizationInformation.ps1 index 772f70fdf3..d58abeab27 100644 --- a/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOrganizationInformation.ps1 +++ b/Diagnostics/HealthChecker/Analyzer/Invoke-AnalyzerOrganizationInformation.ps1 @@ -145,5 +145,67 @@ function Invoke-AnalyzerOrganizationInformation { Add-AnalyzedResultInformation @params } + # Legacy Exchange security groups (security best practice cleanup). + # These groups were created by Exchange 2000/2003, are not used by modern Exchange, and still + # carry elevated AD permissions. We surface them here (Organization Information) as a Yellow + # best-practice warning - this is a configuration cleanup recommendation, not a vulnerability. + $legacySecurityGroups = $organizationInformation.LegacySecurityGroups + + if ($null -ne $legacySecurityGroups -and $legacySecurityGroups.Count -gt 0) { + Write-Verbose "Found $($legacySecurityGroups.Count) legacy Exchange security group(s) to review" + + $params = $baseParams + @{ + Name = "Legacy Exchange Security Groups" + Details = $true + DisplayWriteType = "Yellow" + DisplayTestingValue = $true + AddHtmlDetailRow = $false + } + Add-AnalyzedResultInformation @params + + $legacyGroupsDisplay = New-Object System.Collections.Generic.List[object] + foreach ($legacyGroup in $legacySecurityGroups) { + $legacyGroupsDisplay.Add([PSCustomObject]@{ + DistinguishedName = $legacyGroup.DistinguishedName + Scope = $legacyGroup.Scope + Members = $legacyGroup.MemberCount + }) + } + + # Legacy groups are always flagged Yellow. The Members cell is only highlighted when the + # group actually has members; an empty Members count (0) is left at the default color. + $legacyGroupsColorizer = { + param ($o, $p) + if ($p -eq "Members") { + if ($o.$p -gt 0) { + "Yellow" + } + } else { + "Yellow" + } + } + + $params = $baseParams + @{ + OutColumns = ([PSCustomObject]@{ + DisplayObject = $legacyGroupsDisplay + ColorizerFunctions = @($legacyGroupsColorizer) + IndentSpaces = 12 + }) + OutColumnsColorTests = @($legacyGroupsColorizer) + HtmlName = "Legacy Exchange Security Groups" + TestingName = "Legacy Exchange Security Groups Table" + } + Add-AnalyzedResultInformation @params + + $params = $baseParams + @{ + Details = "These legacy Exchange security groups are not used by modern Exchange and carry elevated AD permissions. As a security best practice, review their membership and delete them if they are no longer required. More Information: https://aka.ms/HC-LegacyExchangeGroups" + DisplayWriteType = "Yellow" + DisplayCustomTabNumber = 1 + } + Add-AnalyzedResultInformation @params + } else { + Write-Verbose "No legacy Exchange security groups found." + } + Write-Verbose "Completed: $($MyInvocation.MyCommand) and took $($stopWatch.Elapsed.TotalSeconds) seconds" } diff --git a/Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Get-ExchangeLegacySecurityGroups.ps1 b/Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Get-ExchangeLegacySecurityGroups.ps1 new file mode 100644 index 0000000000..da5926d9aa --- /dev/null +++ b/Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Get-ExchangeLegacySecurityGroups.ps1 @@ -0,0 +1,72 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +. $PSScriptRoot\..\..\..\..\Shared\ErrorMonitorFunctions.ps1 +. $PSScriptRoot\..\..\..\..\Shared\ActiveDirectoryFunctions\Search-AllActiveDirectoryDomains.ps1 + +<# +.DESCRIPTION + Searches the forest for legacy Exchange security groups that were created by Exchange 2000/2003 + and are no longer used by modern Exchange. These groups still carry elevated Active Directory + permissions, so as a security best practice they should be reviewed and removed if unused. + + Search-AllActiveDirectoryDomains binds to a Global Catalog, so a single forest-wide query covers + every domain in one round-trip - group objects of all scopes (Global / Domain Local / Universal) + are published to the GC, and there is no need to iterate each domain individually. + + NOTE: The GC replicates the full membership only for Universal groups. For Global and Domain + Local groups the 'member' attribute is not available from the GC, so MemberCount is best-effort + for those scopes and is informational only. + + Reference: https://learn.microsoft.com/en-us/previous-versions/office/exchange-server-2010/gg576862(v=exchg.141) +#> +function Get-ExchangeLegacySecurityGroups { + [CmdletBinding()] + param() + begin { + Write-Verbose "Calling: $($MyInvocation.MyCommand)" + $legacyGroups = New-Object 'System.Collections.Generic.List[object]' + + # Default sAMAccountName values of the legacy Exchange groups. + # NOTE: Renamed groups cannot be reliably detected by name. + $legacyGroupNames = @( + "Exchange Domain Servers", # Global (one per domain) + "Exchange Enterprise Servers", # Domain local (forest root) + "Exchange Recipient Administrators" # Universal (legacy Exchange 2007 role group) + ) + } process { + try { + $filter = "(&(objectClass=group)(|" + + (($legacyGroupNames | ForEach-Object { "(sAMAccountName=$_)" }) -join "") + "))" + $propertiesToLoad = @("distinguishedName", "sAMAccountName", "groupType", "member") + + $searchResults = Search-AllActiveDirectoryDomains -Filter $filter -PropertiesToLoad $propertiesToLoad + + foreach ($result in $searchResults) { + $distinguishedName = [string]($result.Properties["distinguishedName"][0]) + Write-Verbose "Found legacy group: $distinguishedName" + + # Decode the group scope from the low bits of the groupType attribute. + $groupTypeValue = [int]($result.Properties["groupType"][0]) + $scope = switch ($groupTypeValue -band 0x0000000E) { + 2 { "Global" } + 4 { "DomainLocal" } + 8 { "Universal" } + default { "Unknown" } + } + + $legacyGroups.Add([PSCustomObject]@{ + Name = [string]($result.Properties["sAMAccountName"][0]) + DistinguishedName = $distinguishedName + Scope = $scope + MemberCount = $result.Properties["member"].Count + }) + } + } catch { + Write-Verbose "Failed to query Active Directory for legacy Exchange security groups. Inner Exception: $_" + Invoke-CatchActions + } + } end { + return $legacyGroups + } +} diff --git a/Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Invoke-JobOrganizationInformation.ps1 b/Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Invoke-JobOrganizationInformation.ps1 index 279cdc3abc..28abd8a339 100644 --- a/Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Invoke-JobOrganizationInformation.ps1 +++ b/Diagnostics/HealthChecker/DataCollection/OrganizationInformation/Invoke-JobOrganizationInformation.ps1 @@ -17,6 +17,7 @@ function Invoke-JobOrganizationInformation { # Build Process to add functions. . $PSScriptRoot\Get-ExchangeAdSchemaInformation.ps1 . $PSScriptRoot\Get-ExchangeDomainsAclPermissions.ps1 + . $PSScriptRoot\Get-ExchangeLegacySecurityGroups.ps1 . $PSScriptRoot\Get-ExchangeWellKnownSecurityGroups.ps1 . $PSScriptRoot\Get-SecurityCve-2021-34470.ps1 . $PSScriptRoot\Get-SecurityCve-2022-21978.ps1 @@ -33,6 +34,7 @@ function Invoke-JobOrganizationInformation { $getOrganizationConfig = $null $domainsAclPermissions = $null $wellKnownSecurityGroups = $null + $legacySecurityGroups = $null $adSchemaInformation = $null $getHybridConfiguration = $null $getPartnerApplication = $null @@ -111,6 +113,7 @@ function Invoke-JobOrganizationInformation { Get-ExchangeAdSchemaInformation | Invoke-RemotePipelineHandler -Result ([ref]$adSchemaInformation) Get-ExchangeDomainsAclPermissions | Invoke-RemotePipelineHandler -Result ([ref]$domainsAclPermissions) Get-ExchangeWellKnownSecurityGroups | Invoke-RemotePipelineHandler -Result ([ref]$wellKnownSecurityGroups) + Get-ExchangeLegacySecurityGroups | Invoke-RemotePipelineHandler -Result ([ref]$legacySecurityGroups) Get-ExchangeADSplitPermissionsEnabled -CatchActionFunction ${Function:Invoke-CatchActions} | Invoke-RemotePipelineHandler -Result ([ref]$isSplitADPermissions) # Exchange Cmdlets @@ -293,6 +296,7 @@ function Invoke-JobOrganizationInformation { GetOrganizationConfig = $getOrganizationConfig DomainsAclPermissions = $domainsAclPermissions WellKnownSecurityGroups = $wellKnownSecurityGroups + LegacySecurityGroups = $legacySecurityGroups AdSchemaInformation = $adSchemaInformation GetHybridConfiguration = $getHybridConfiguration GetPartnerApplication = $getPartnerApplication diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 index d175717b33..e34f1fd57c 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Main.Tests.ps1 @@ -347,6 +347,7 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange SE" { Assert-MockCalled Get-IISModules -Exactly 1 -Scope Context Assert-MockCalled Get-ExchangeDiagnosticInfo -Exactly 2 -Scope Context Assert-MockCalled Get-ExchangeADSplitPermissionsEnabled -Exactly 1 -Scope Context + Assert-MockCalled Search-AllActiveDirectoryDomains -Exactly 1 -Scope Context Assert-MockCalled Get-DynamicDistributionGroup -Exactly 1 -Scope Context Assert-MockCalled Get-ActiveSyncVirtualDirectory -Exactly 1 -Scope Context Assert-MockCalled Get-AutodiscoverVirtualDirectory -Exactly 1 -Scope Context diff --git a/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Scenarios.Tests.ps1 b/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Scenarios.Tests.ps1 index 28b6dacd4f..0e76cf7504 100644 --- a/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Scenarios.Tests.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthChecker.SE.Scenarios.Tests.ps1 @@ -474,6 +474,16 @@ Describe "Exchange SE Scenarios Testing" { # Reset AuthServer to return both ACS + EvoSTS (S1 sets it to "ACS") $Script:GetAuthServerMockDataType = "All" + # Legacy Exchange security groups present (security best practice cleanup). + # Mock the underlying AD search so the real Get-ExchangeLegacySecurityGroups logic runs. + Mock Search-AllActiveDirectoryDomains -ParameterFilter { $Filter -like "*Exchange Domain Servers*" } -MockWith { + return @( + (NewMockAdSearchResult -DistinguishedName "CN=Exchange Domain Servers,CN=Users,DC=contoso,DC=com" -SamAccountName "Exchange Domain Servers" -GroupType -2147483646), + (NewMockAdSearchResult -DistinguishedName "CN=Exchange Enterprise Servers,CN=Users,DC=contoso,DC=com" -SamAccountName "Exchange Enterprise Servers" -GroupType -2147483644), + (NewMockAdSearchResult -DistinguishedName "CN=Exchange Recipient Administrators,OU=Microsoft Exchange Security Groups,DC=contoso,DC=com" -SamAccountName "Exchange Recipient Administrators" -GroupType -2147483640 -MemberCount 3) + ) + } + SetDefaultRunOfHealthChecker "Debug_SE_Scenario3_Physical_Results.xml" } @@ -482,6 +492,31 @@ Describe "Exchange SE Scenarios Testing" { TestObjectMatch "Dynamic Distribution Group Public Folder Mailboxes Count" 2 -WriteType "Red" } + It "Legacy Exchange Security Groups Detected" { + SetActiveDisplayGrouping "Organization Information" + TestObjectMatch "Legacy Exchange Security Groups" $true -WriteType "Yellow" + } + + It "Legacy Exchange Security Groups Parsed Correctly" { + # Directly exercises the real Get-ExchangeLegacySecurityGroups parsing (scope decode from + # groupType, member count, DN) against the mocked Search-AllActiveDirectoryDomains data. + $legacyGroups = @(Get-ExchangeLegacySecurityGroups | Where-Object { $null -ne $_.DistinguishedName }) + $legacyGroups.Count | Should -Be 3 + + $domainServers = $legacyGroups | Where-Object { $_.Name -eq "Exchange Domain Servers" } + $domainServers.Scope | Should -Be "Global" + $domainServers.MemberCount | Should -Be 0 + $domainServers.DistinguishedName | Should -Be "CN=Exchange Domain Servers,CN=Users,DC=contoso,DC=com" + + $enterpriseServers = $legacyGroups | Where-Object { $_.Name -eq "Exchange Enterprise Servers" } + $enterpriseServers.Scope | Should -Be "DomainLocal" + $enterpriseServers.MemberCount | Should -Be 0 + + $recipientAdmins = $legacyGroups | Where-Object { $_.Name -eq "Exchange Recipient Administrators" } + $recipientAdmins.Scope | Should -Be "Universal" + $recipientAdmins.MemberCount | Should -Be 3 + } + It "Extended Protection Enabled" { SetActiveDisplayGrouping "Exchange Information" TestObjectMatch "Extended Protection Enabled (Any VDir)" $true diff --git a/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 b/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 index 0612f073ff..c8432194f6 100644 --- a/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 +++ b/Diagnostics/HealthChecker/Tests/HealthCheckerTest.CommonMocks.NotPublished.ps1 @@ -154,6 +154,39 @@ Mock Get-ExchangeWellKnownSecurityGroups { return Import-Clixml "$Script:MockDataCollectionRoot\Exchange\GetExchangeWellKnownSecurityGroups.xml" } +# Builds an object shaped like a System.DirectoryServices.SearchResult so the real +# Get-ExchangeLegacySecurityGroups parsing logic (scope decode, member count, DN handling) is tested. +# An absent 'member' (empty array) mimics the GC not replicating membership for Global/Domain Local groups. +function NewMockAdSearchResult { + [CmdletBinding()] + param( + [string]$DistinguishedName, + [string]$SamAccountName, + [int]$GroupType, + [int]$MemberCount = 0 + ) + + $members = @() + if ($MemberCount -gt 0) { + $members = @(1..$MemberCount | ForEach-Object { "CN=Member$_,CN=Users,DC=contoso,DC=com" }) + } + + return [PSCustomObject]@{ + Properties = @{ + distinguishedName = @($DistinguishedName) + sAMAccountName = @($SamAccountName) + groupType = @($GroupType) + member = $members + } + } +} + +# Default: the legacy Exchange security groups are not present. Specific tests override this with a +# -ParameterFilter on $Filter to return mock search results for the legacy groups query. +Mock Search-AllActiveDirectoryDomains { + return @() +} + Mock Get-HttpProxySetting { return Import-Clixml "$Script:MockDataCollectionRoot\OS\GetHttpProxySetting.xml" }