maester-tests/Maester/Entra/Test-EntraRecommendations.Tests.ps1
|
BeforeDiscovery { try { $EntraRecommendationsResponse = Invoke-MtGraphRequest -DisableCache -ApiVersion beta -RelativeUri 'directory/recommendations?$expand=impactedResources' -OutputType Hashtable $EntraRecommendations = @($EntraRecommendationsResponse.value) Write-Verbose "Found $($EntraRecommendations.Count) Entra recommendations" } catch { Write-Verbose 'Authentication needed. Please call Connect-MgGraph.' } } Describe "Maester/Entra" -Tag "Maester", "Entra", "Recommendation" -ForEach $EntraRecommendations { # Define the test name and Id for each Entra recommendation. $RecommendationId = $_.id It "MT.1024.$($RecommendationId -replace '^[^_]+_', ''): $($_.displayName). See https://maester.dev/docs/tests/MT.1024" -Tag "MT.1024", "$($_.recommendationType)" { $RecommendationId = $_.id $EntraPremiumRecommendations = @( "insiderRiskPolicy", "userRiskPolicy", "signinRiskPolicy" ) #region Build test result markdown $recommendationUrl = "https://entra.microsoft.com/#view/Microsoft_AAD_IAM/RecommendationDetails.ReactView/recommendationId/$($RecommendationId)" $recommendationLinkMd = "`n`n➡️ Open [Recommendation - $($_.displayName)]($recommendationUrl) in the Entra admin portal.`n`n*Note: If the recommendation is not applicable for your tenant, it can be marked as **Dismissed** for Maester to skip it in the future.*" $impactedResourcesList = "" if ($_.status -ne 'completedBySystem' -and $_.impactedResources) { $impactedResourcesList = "`n`n#### Impacted resources`n`n| Status | Name | First detected |`n" $impactedResourcesList += "| --- | --- | --- |`n" foreach ($resource in $_.impactedResources) { if ($resource.status -eq 'completedBySystem') { $resourceResult = "✅ Pass" } else { $resourceResult = "❌ Fail" } $impactedResourcesList += "| $($resourceResult) | [$($resource.displayName)]($($resource.portalUrl)) | $($resource.addedDateTime) |`n" } } #end if status -ne 'completedBySystem' and impactedResources $resultMd = $_.insights + $impactedResourcesList + $recommendationLinkMd #endregion Build test result markdown #region Build test description markdown $actionSteps = $_.actionSteps | Sort-Object -Property 'stepNumber' | ForEach-Object { $actionLink = "" if ($_.actionUrl.url) { $actionLink = " [$($_.actionUrl.displayName)]($($_.actionUrl.url.replace('\l','#')))." } ($_.text.replace("<br>","`n").replace("<br/>","`n").split("`n").trim() -replace "<a.+?href=[`"']([^`"']+)[`"'].+?>([^<]+)<\/a>", '[$2]($1)') + $actionLink } $actionSteps = $actionSteps -join "`n`n" $descriptionMd = "$($_.benefits)`n`n#### Remediation action:`n`n${actionSteps}`n`n**Impact:** $($_.remediationImpact)`n`n#### Related links:`n`n* [$($_.displayName) - Microsoft Entra admin center]($recommendationUrl)" #endregion Build test description markdown $textInfo = (Get-Culture).TextInfo $priority = $textInfo.ToTitleCase($_.priority) $EntraIDPlan = Get-MtLicenseInformation -Product "EntraID" if ( $EntraIDPlan -ne "P2" ) { $EntraPremiumRecommendations | ForEach-Object { if ( $RecommendationId -match "$($_)$" ) { Add-MtTestResultDetail -Description $descriptionMd -Severity $priority -SkippedBecause NotLicensedEntraIDP2 return $null } } } if ( $_.status -match "dismissed" ) { Add-MtTestResultDetail -Description $descriptionMd -Severity $priority -SkippedBecause Custom -SkippedCustomReason "This recommendation has been **Dismissed** by an administrator.`n`nIf this test is valid for your tenant you can change its state from **Dismissed** to **Active**. $recommendationLinkMd" return $null } # Break-glass (emergency access) accounts are intentionally excluded from risk-based Conditional # Access policies to avoid locking out the accounts needed to recover the tenant, so the sign-in # risk and user risk recommendations always flag them as impacted resources that are never # remediated. When the configured break-glass accounts are the only accounts still flagged, they # are the sole reason the recommendation is not complete and must not fail the test. See #2103. $breakGlassAwareRecommendationTypes = @('userRiskPolicy', 'signinRiskPolicy') if ( $_.status -ne 'completedBySystem' -and $_.recommendationType -in $breakGlassAwareRecommendationTypes ) { try { $breakGlassObjectId = @((Get-MtEmergencyAccessAccount).ObjectId) } catch { # A break-glass account that cannot be resolved must not become a false pass. Leave the # list empty so the exclusion does not apply and the recommendation is evaluated as-is. $breakGlassObjectId = @() Write-Verbose "MT.1024: could not resolve emergency access accounts, evaluating recommendation without break-glass exclusion. $($_.Exception.Message)" } $onlyBreakGlassImpacted = Test-MtRecommendationBreakGlassOnly -ImpactedResources $_.impactedResources -BreakGlassObjectId $breakGlassObjectId if ( $onlyBreakGlassImpacted ) { $breakGlassNames = @($_.impactedResources | Where-Object { $_.status -ne 'completedBySystem' } | ForEach-Object { $_.displayName }) -join ', ' $breakGlassNote = "`n`n> ℹ️ The only impacted resources are configured emergency access (break-glass) accounts, which are intentionally excluded from risk-based policies and are reported here for information only: $breakGlassNames." Add-MtTestResultDetail -Description $descriptionMd -Severity $priority -Result ($resultMd + $breakGlassNote) $onlyBreakGlassImpacted | Should -BeTrue -Because "the only impacted resources are the configured emergency access (break-glass) accounts, which are intentionally excluded from risk-based policies" return $null } } Add-MtTestResultDetail -Description $descriptionMd -Severity $priority -Result $resultMd # Actual test $_.status | Should -Be "completedBySystem" -Because $_.benefits } } |