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 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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"