From f85646aa3dc5ad1a7444afd5423ca9c44da48286 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 9 May 2026 14:20:59 +0200 Subject: [PATCH 01/13] Test-MtCisPasswordExpiry: Only Check domains with isVerified: true to avoid false positives with MD double newline fix --- powershell/public/cis/Test-MtCisPasswordExpiry.md | 1 - powershell/public/cis/Test-MtCisPasswordExpiry.ps1 | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.md b/powershell/public/cis/Test-MtCisPasswordExpiry.md index c57148b15..e217d0628 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.md +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.md @@ -13,7 +13,6 @@ When setting passwords not to expire it is important to have other controls in p * Educate users to not reuse organization passwords anywhere else. * Enforce Multi-Factor Authentication registration for all users. - #### Remediation action: To set Office 365 passwords are set to never expire: diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 8af471fb4..14da160bb 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -29,7 +29,7 @@ $domains = Invoke-MtGraphRequest -RelativeUri 'domains' Write-Verbose 'Get domains where passwords are set to expire' - $result = $domains | Where-Object { ($_.PasswordValidityPeriodInDays -ne '2147483647') -and ($_.authenticationType -eq "Managed") } + $result = $domains | Where-Object { ($_.PasswordValidityPeriodInDays -ne '2147483647') -and ($_.authenticationType -eq "Managed") -and ($_.isVerified -eq $true) } $testResult = ($result | Measure-Object).Count -eq 0 From c1daf37d870c116907e804d7b93ebd3bf2e700d2 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 16 May 2026 09:10:19 +0200 Subject: [PATCH 02/13] Adding failsaife mesures for PasswordValidityPeriodInDays being a string --- .../public/cis/Test-MtCisPasswordExpiry.ps1 | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 14da160bb..0616fab1a 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -28,8 +28,24 @@ Write-Verbose 'Get domain details the password expiry period' $domains = Invoke-MtGraphRequest -RelativeUri 'domains' - Write-Verbose 'Get domains where passwords are set to expire' - $result = $domains | Where-Object { ($_.PasswordValidityPeriodInDays -ne '2147483647') -and ($_.authenticationType -eq "Managed") -and ($_.isVerified -eq $true) } + Write-Verbose 'Get verified domains where passwords are set to expire' + + $noPasswordExpiryPeriodInDays = [int]::MaxValue + + $result = $domains | Where-Object { + if (($_.authenticationType -ne "Managed") -or ($_.isVerified -ne $true)) { + return $false + } + $passwordValidityPeriodInDays = 0 + $rawPasswordValidityPeriodInDays = $_.PasswordValidityPeriodInDays + if (($null -eq $rawPasswordValidityPeriodInDays) -or ($rawPasswordValidityPeriodInDays -is [bool])) { + return $false + } + if (-not [int]::TryParse($rawPasswordValidityPeriodInDays.ToString(), [ref]$passwordValidityPeriodInDays)) { + return $false + } + return $passwordValidityPeriodInDays -ne $noPasswordExpiryPeriodInDays + } $testResult = ($result | Measure-Object).Count -eq 0 From 3e71511f15cd7992e1d3d367984c2d8302b9e5d6 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sat, 16 May 2026 09:53:13 +0200 Subject: [PATCH 03/13] added comments after testing for clarification and improved verbosity --- powershell/public/cis/Test-MtCisPasswordExpiry.ps1 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 0616fab1a..112bdbcb4 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -28,22 +28,27 @@ Write-Verbose 'Get domain details the password expiry period' $domains = Invoke-MtGraphRequest -RelativeUri 'domains' - Write-Verbose 'Get verified domains where passwords are set to expire' + Write-Verbose 'Get verified and managed domains where passwords are set to expire' $noPasswordExpiryPeriodInDays = [int]::MaxValue $result = $domains | Where-Object { + # Filter out domains that are not 'managed' or not verified, as password policies do not apply to them if (($_.authenticationType -ne "Managed") -or ($_.isVerified -ne $true)) { return $false } + $passwordValidityPeriodInDays = 0 - $rawPasswordValidityPeriodInDays = $_.PasswordValidityPeriodInDays - if (($null -eq $rawPasswordValidityPeriodInDays) -or ($rawPasswordValidityPeriodInDays -is [bool])) { + $domainPasswordValidityPeriodInDays = $_.PasswordValidityPeriodInDays + # If null or a boolean, the password expiry period is not set, and passwords do not expire. + # Return false to indicate this domain does not fail the test. + if (($null -eq $domainPasswordValidityPeriodInDays) -or ($domainPasswordValidityPeriodInDays -is [bool])) { return $false } - if (-not [int]::TryParse($rawPasswordValidityPeriodInDays.ToString(), [ref]$passwordValidityPeriodInDays)) { + if (-not [int]::TryParse($domainPasswordValidityPeriodInDays.ToString(), [ref]$passwordValidityPeriodInDays)) { return $false } + # If valid integer, check if equal to the value that indicates no password expiry (MaxValue). return $passwordValidityPeriodInDays -ne $noPasswordExpiryPeriodInDays } From 3d201cccba94656803d74bb248b19880d5d678bf Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Mon, 18 May 2026 16:07:37 -0400 Subject: [PATCH 04/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- powershell/public/cis/Test-MtCisPasswordExpiry.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 112bdbcb4..782d858b5 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -40,13 +40,13 @@ $passwordValidityPeriodInDays = 0 $domainPasswordValidityPeriodInDays = $_.PasswordValidityPeriodInDays - # If null or a boolean, the password expiry period is not set, and passwords do not expire. - # Return false to indicate this domain does not fail the test. + # For verified and managed domains, only the known no-expiry numeric value is compliant. + # Treat null, boolean, or unparsable values as failing so the test does not pass on unknown data. if (($null -eq $domainPasswordValidityPeriodInDays) -or ($domainPasswordValidityPeriodInDays -is [bool])) { - return $false + return $true } if (-not [int]::TryParse($domainPasswordValidityPeriodInDays.ToString(), [ref]$passwordValidityPeriodInDays)) { - return $false + return $true } # If valid integer, check if equal to the value that indicates no password expiry (MaxValue). return $passwordValidityPeriodInDays -ne $noPasswordExpiryPeriodInDays From 19ef57d850f732ecaaa9269377d6a7da1d0c5ad2 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Wed, 20 May 2026 07:59:07 +0200 Subject: [PATCH 05/13] added skip result for unmanaged or unverified domains --- .../public/cis/Test-MtCisPasswordExpiry.ps1 | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 782d858b5..3f833379c 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -32,21 +32,28 @@ $noPasswordExpiryPeriodInDays = [int]::MaxValue - $result = $domains | Where-Object { - # Filter out domains that are not 'managed' or not verified, as password policies do not apply to them - if (($_.authenticationType -ne "Managed") -or ($_.isVerified -ne $true)) { - return $false + $excludedDomains = @() + $applicableDomains = @() + foreach ($domain in $domains) { + # Password policy checks apply only to managed and verified domains. + if (($domain.authenticationType -ne "Managed") -or ($domain.isVerified -ne $true)) { + $excludedDomains += $domain + continue } + $applicableDomains += $domain + } + + $result = $applicableDomains | Where-Object { $passwordValidityPeriodInDays = 0 $domainPasswordValidityPeriodInDays = $_.PasswordValidityPeriodInDays - # For verified and managed domains, only the known no-expiry numeric value is compliant. - # Treat null, boolean, or unparsable values as failing so the test does not pass on unknown data. + # If null or a boolean, the password expiry period is not set, and passwords do not expire. + # Return false to indicate this domain does not fail the test. if (($null -eq $domainPasswordValidityPeriodInDays) -or ($domainPasswordValidityPeriodInDays -is [bool])) { - return $true + return $false } if (-not [int]::TryParse($domainPasswordValidityPeriodInDays.ToString(), [ref]$passwordValidityPeriodInDays)) { - return $true + return $false } # If valid integer, check if equal to the value that indicates no password expiry (MaxValue). return $passwordValidityPeriodInDays -ne $noPasswordExpiryPeriodInDays @@ -64,7 +71,9 @@ $resultMd += "| --- | --- |`n" foreach ($item in $domains) { $itemResult = '❌ Fail' - if ($item.id -notin $result.id) { + if ($item.id -in $excludedDomains.id) { + $itemResult = '⏭️ Skip' + } elseif ($item.id -notin $result.id) { $itemResult = '✅ Pass' } $resultMd += "| $($item.Id) | $($itemResult) |`n" From 83fef7597789af2b0430f419a35d603c6042aae6 Mon Sep 17 00:00:00 2001 From: Sam Erde <20478745+SamErde@users.noreply.github.com> Date: Fri, 22 May 2026 07:36:52 -0400 Subject: [PATCH 06/13] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- powershell/public/cis/Test-MtCisPasswordExpiry.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 3f833379c..26fd4e03b 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -25,7 +25,7 @@ } try { - Write-Verbose 'Get domain details the password expiry period' + Write-Verbose 'Get domain details for the password expiry period' $domains = Invoke-MtGraphRequest -RelativeUri 'domains' Write-Verbose 'Get verified and managed domains where passwords are set to expire' From 8ba4cab9bc75c7a709d434da0d3073095d047274 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Fri, 22 May 2026 20:53:02 +0200 Subject: [PATCH 07/13] Fixing result table headers to match output --- powershell/public/cis/Test-MtCisPasswordExpiry.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 3f833379c..c9be008af 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -67,7 +67,7 @@ $testResultMarkdown = "Your tenant has 1 or more 'managed' domains which expire passwords:`n`n%TestResult%" } - $resultMd = "| Display Name | Domain |`n" + $resultMd = "| Domain | Result |`n" $resultMd += "| --- | --- |`n" foreach ($item in $domains) { $itemResult = '❌ Fail' From 7ab7f04e806cbc6bbe478c71ff001ebcb6e68806 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Fri, 22 May 2026 20:53:20 +0200 Subject: [PATCH 08/13] fix verbose output grammar --- powershell/public/cis/Test-MtCisPasswordExpiry.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index c9be008af..507967a70 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -25,7 +25,7 @@ } try { - Write-Verbose 'Get domain details the password expiry period' + Write-Verbose 'Get domain details for the password expiry period' $domains = Invoke-MtGraphRequest -RelativeUri 'domains' Write-Verbose 'Get verified and managed domains where passwords are set to expire' From 8f005fe028b18dffd56d6b699830d2d0e8b40133 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Fri, 22 May 2026 20:55:26 +0200 Subject: [PATCH 09/13] fix result markdown output to clarify managed and verified domains --- powershell/public/cis/Test-MtCisPasswordExpiry.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 index 507967a70..b5aae505e 100644 --- a/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 +++ b/powershell/public/cis/Test-MtCisPasswordExpiry.ps1 @@ -62,9 +62,9 @@ $testResult = ($result | Measure-Object).Count -eq 0 if ($testResult) { - $testResultMarkdown = "Well done. Your tenant passwords are not set to expire on all your 'managed' domains:`n`n%TestResult%" + $testResultMarkdown = "Well done. Your tenant passwords are not set to expire on all your 'managed' and 'verified' domains:`n`n%TestResult%" } else { - $testResultMarkdown = "Your tenant has 1 or more 'managed' domains which expire passwords:`n`n%TestResult%" + $testResultMarkdown = "Your tenant has 1 or more 'managed' and 'verified' domains which expire passwords:`n`n%TestResult%" } $resultMd = "| Domain | Result |`n" From 001e12299c576046d9a271243de2d8fcc059d283 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 9 Aug 2026 19:17:56 +0200 Subject: [PATCH 10/13] docs: updated function doc for MT.1020 --- .../entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 b/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 index 4b6a63a46..a6360bf37 100644 --- a/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 +++ b/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 @@ -1,14 +1,16 @@ function Test-MtCaExclusionForDirectorySyncAccount { <# .Synopsis - Checks if all Conditional Access policies scoped to all cloud apps and all users exclude the directory synchronization accounts + Checks if all Conditional Access policies scoped to all cloud apps and all users exclude the directory synchronization accounts for Entra Id Connect. .Description The directory synchronization accounts are used to synchronize the on-premises directory with Entra ID. These accounts should be excluded from all Conditional Access policies scoped to all cloud apps and all users. - Entra ID connect does not support multifactor authentication. Restrict access with these accounts to trusted networks. + With latest update, Entra ID connect does support modern authentication. + As environments are just moving to modern authentication, we keep this test. If you are using modern authentication for Entra ID connect, you can mute this test. + .Example Test-MtCaExclusionForDirectorySyncAccount @@ -57,7 +59,7 @@ # account. Service principals are not subject to Conditional Access policies and do not need to be # excluded from them. $userSyncMembers = @($Members | Where-Object { $_.'@odata.type' -ne '#microsoft.graph.servicePrincipal' }) - $spSyncMembers = @($Members | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.servicePrincipal' }) + $spSyncMembers = @($Members | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.servicePrincipal' }) if ( $userSyncMembers.Count -eq 0 -and $spSyncMembers.Count -gt 0 ) { $spNames = ( $spSyncMembers | Where-Object { $_.displayName } | ForEach-Object { $_.displayName } ) -join ', ' From 49b734adcdc9fe13b2dcf494d5f732e01cb60b9f Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <13959569+blindzero@users.noreply.github.com> Date: Sun, 9 Aug 2026 21:13:07 +0200 Subject: [PATCH 11/13] docs: precised documentation of MT.1020 according to review recommendation --- .../entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 b/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 index a6360bf37..7cd86d21c 100644 --- a/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 +++ b/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 @@ -1,15 +1,16 @@ function Test-MtCaExclusionForDirectorySyncAccount { <# .Synopsis - Checks if all Conditional Access policies scoped to all cloud apps and all users exclude the directory synchronization accounts for Entra Id Connect. + Checks if all Conditional Access policies scoped to all cloud apps and all users exclude the directory synchronization accounts for Microsoft Entra Connect.. .Description - The directory synchronization accounts are used to synchronize the on-premises directory with Entra ID. + The directory synchronization accounts are used to synchronize the on-premises directory with Microsoft Entra Connect. These accounts should be excluded from all Conditional Access policies scoped to all cloud apps and all users. Restrict access with these accounts to trusted networks. - With latest update, Entra ID connect does support modern authentication. - As environments are just moving to modern authentication, we keep this test. If you are using modern authentication for Entra ID connect, you can mute this test. + With latest update, Microsoft Entra Connect does support modern authentication. + As environments are just moving to modern authentication, we keep this test. + Mute this test, when the tenant has migrated every directory synchronization account to Application-Based Authentication with the ABA service principal taking over sync, so no directory sync user users remain. If this paragraph refers to that capability, include the supported minimum version: Microsoft Entra Connect 2.5.76.0 or later. .Example Test-MtCaExclusionForDirectorySyncAccount From 819c396cc88becae59c1612fa5633446eb2d37b7 Mon Sep 17 00:00:00 2001 From: Merill Fernando Date: Thu, 13 Aug 2026 20:57:21 +1000 Subject: [PATCH 12/13] fix(mt1020): clarify Entra Connect ABA guidance --- ...t-MtCaExclusionForDirectorySyncAccount.ps1 | 35 +++++++++------ ...ExclusionForDirectorySyncAccount.Tests.ps1 | 43 ++++++++++++------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 b/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 index 7cd86d21c..e17f291dc 100644 --- a/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 +++ b/powershell/public/maester/entra/Test-MtCaExclusionForDirectorySyncAccount.ps1 @@ -1,22 +1,32 @@ function Test-MtCaExclusionForDirectorySyncAccount { <# .Synopsis - Checks if all Conditional Access policies scoped to all cloud apps and all users exclude the directory synchronization accounts for Microsoft Entra Connect.. + Checks whether Conditional Access policies exclude user-based Microsoft Entra Connect synchronization identities. .Description - The directory synchronization accounts are used to synchronize the on-premises directory with Microsoft Entra Connect. - These accounts should be excluded from all Conditional Access policies scoped to all cloud apps and all users. - Restrict access with these accounts to trusted networks. + Microsoft Entra Connect uses a connector identity to synchronize an on-premises directory with Microsoft Entra ID. + Legacy installations can use a user-based directory synchronization account. These accounts should be excluded from + Conditional Access policies scoped to all cloud apps and all users, and their access should be restricted to trusted + networks. - With latest update, Microsoft Entra Connect does support modern authentication. - As environments are just moving to modern authentication, we keep this test. - Mute this test, when the tenant has migrated every directory synchronization account to Application-Based Authentication with the ABA service principal taking over sync, so no directory sync user users remain. If this paragraph refers to that capability, include the supported minimum version: Microsoft Entra Connect 2.5.76.0 or later. + New installations of Microsoft Entra Connect 2.5.76.0 or later use application-based authentication by default, with a + service principal and certificate instead of a user account and password. Existing installations do not switch to + application-based authentication automatically. + + This test evaluates user principals assigned to the directory synchronization roles. It passes automatically when no + user principals remain, because Conditional Access user exclusions do not apply to service principals; the test does not + need to be muted. To verify the authentication method currently used, run Get-ADSyncEntraConnectorCredential on every + Microsoft Entra Connect server and confirm that ConnectorIdentityType is Application. After verifying the migration, + remove the legacy directory synchronization account or remove its directory synchronization role assignment. .Example Test-MtCaExclusionForDirectorySyncAccount .LINK https://maester.dev/docs/commands/Test-MtCaExclusionForDirectorySyncAccount + + .LINK + https://learn.microsoft.com/entra/identity/hybrid/connect/authenticate-application-id #> [CmdletBinding()] [OutputType([bool])] @@ -54,18 +64,15 @@ return $true } - # Classify members: user accounts (subject to CA policies) vs. service principals (not subject to CA). - # As of Microsoft Entra Connect v2.5.76.0, directory sync supports Application-Based Authentication - # (ABA), where sync is performed by a registered service principal rather than a dedicated user - # account. Service principals are not subject to Conditional Access policies and do not need to be - # excluded from them. + # Classify role members by whether Conditional Access user targeting applies. Role membership establishes + # whether user principals need CA handling, but it does not prove which credential an active Connect server uses. $userSyncMembers = @($Members | Where-Object { $_.'@odata.type' -ne '#microsoft.graph.servicePrincipal' }) - $spSyncMembers = @($Members | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.servicePrincipal' }) + $spSyncMembers = @($Members | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.servicePrincipal' }) if ( $userSyncMembers.Count -eq 0 -and $spSyncMembers.Count -gt 0 ) { $spNames = ( $spSyncMembers | Where-Object { $_.displayName } | ForEach-Object { $_.displayName } ) -join ', ' if ( -not $spNames ) { $spNames = 'unknown' } - Add-MtTestResultDetail -Description $testDescription -Result "This tenant uses Application-Based Authentication (ABA) for directory synchronization. As of Microsoft Entra Connect v2.5.76.0, sync can be performed by a registered service principal ($spNames) rather than a dedicated user account. Service principals are not subject to Conditional Access policies; no CA exclusions are required." + Add-MtTestResultDetail -Description $testDescription -Result "Only service principals are assigned to the directory synchronization roles ($spNames). Conditional Access user exclusions do not apply to service principals, so this test is not applicable. Role membership alone does not confirm that an active Microsoft Entra Connect server uses application-based authentication; verify each server with Get-ADSyncEntraConnectorCredential." return $true } diff --git a/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 b/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 index dde52e707..0c7ef2d05 100644 --- a/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 +++ b/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 @@ -104,21 +104,34 @@ } } - Context 'Service principal-only members (ABA — Application-Based Authentication, Entra Connect v2.5.76.0+)' { - - BeforeEach { - # Only a service principal is in the role — this is the ABA pattern (Entra Connect v2.5.76.0+). - # The new code returns true early (before iterating policies) with an informative ABA message. - Mock -ModuleName Maester Get-MtRoleMember { return $script:syncServicePrincipal } - Mock -ModuleName Maester Get-MtConditionalAccessPolicy { - return @(New-CaPolicy -ExcludeUsers @() -ExcludeRoles @()) - } - } - - It 'Should return true because ABA service principals are not subject to CA policies' { - Test-MtCaExclusionForDirectorySyncAccount | Should -BeTrue - } - } + Context 'Service principal-only synchronization role members' { + + BeforeEach { + # Role membership is sufficient to determine CA applicability, but does not prove which connector identity is active. + Mock -ModuleName Maester Get-MtRoleMember { return $script:syncServicePrincipal } + Mock -ModuleName Maester Get-MtConditionalAccessPolicy { + return @(New-CaPolicy -ExcludeUsers @() -ExcludeRoles @()) + } + } + + It 'Should return true without querying CA policies because user exclusions do not apply' { + Test-MtCaExclusionForDirectorySyncAccount | Should -BeTrue + + Should -Invoke Get-MtConditionalAccessPolicy -ModuleName Maester -Times 0 -Exactly + } + + It 'Should not claim that role membership proves application-based authentication is active' { + Test-MtCaExclusionForDirectorySyncAccount | Should -BeTrue + + Should -Invoke Add-MtTestResultDetail -ModuleName Maester -Times 1 -Exactly -ParameterFilter { + $Result -like 'Only service principals are assigned*' -and + $Result -like '*this test is not applicable*' -and + $Result -like '*Role membership alone does not confirm*' -and + $Result -like '*Get-ADSyncEntraConnectorCredential*' -and + $Result -notlike 'This tenant uses Application-Based Authentication*' + } + } + } Context 'Policy does not target all applications' { From 076210e9934f82c60be27b748316d72029cc5fed Mon Sep 17 00:00:00 2001 From: Merill Fernando Date: Thu, 13 Aug 2026 21:34:44 +1000 Subject: [PATCH 13/13] test(mt1020): strengthen obsolete claim assertion --- .../Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 b/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 index 0c7ef2d05..3599cc1e9 100644 --- a/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 +++ b/powershell/tests/functions/Test-MtCaExclusionForDirectorySyncAccount.Tests.ps1 @@ -128,7 +128,7 @@ $Result -like '*this test is not applicable*' -and $Result -like '*Role membership alone does not confirm*' -and $Result -like '*Get-ADSyncEntraConnectorCredential*' -and - $Result -notlike 'This tenant uses Application-Based Authentication*' + $Result -notlike '*This tenant uses Application-Based Authentication*' } } }