diff --git a/src/Maester.psd1 b/src/Maester.psd1 index 8133c6c3..4c4e130e 100644 --- a/src/Maester.psd1 +++ b/src/Maester.psd1 @@ -8,129 +8,135 @@ @{ -# Script module or binary module file associated with this manifest. -RootModule = 'Maester.psm1' + # Script module or binary module file associated with this manifest. + RootModule = 'Maester.psm1' -# Version number of this module. -ModuleVersion = '0.0.11' + # Version number of this module. + ModuleVersion = '0.0.11' -# Supported PSEditions -CompatiblePSEditions = 'Core', 'Desktop' + # Supported PSEditions + CompatiblePSEditions = 'Core', 'Desktop' -# ID used to uniquely identify this module -GUID = '502a7fe7-b1ae-4bf5-98db-00831b14ed6f' + # ID used to uniquely identify this module + GUID = '502a7fe7-b1ae-4bf5-98db-00831b14ed6f' -# Author of this module -Author = 'Microsoft Identity' + # Author of this module + Author = 'Microsoft Identity' -# Company or vendor of this module -CompanyName = 'Microsoft Corporation' + # Company or vendor of this module + CompanyName = 'Microsoft Corporation' -# Copyright statement for this module -Copyright = 'Microsoft Corporation. All rights reserved.' + # Copyright statement for this module + Copyright = 'Microsoft Corporation. All rights reserved.' -# Description of the functionality provided by this module -Description = 'This cmdlets in this module help you write Pester tests for your Microsoft 365 tenant configuration.' + # Description of the functionality provided by this module + Description = 'This cmdlets in this module help you write Pester tests for your Microsoft 365 tenant configuration.' -# Minimum version of the PowerShell engine required by this module -PowerShellVersion = '5.1' + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '5.1' -# Name of the PowerShell host required by this module -# PowerShellHostName = '' + # Name of the PowerShell host required by this module + # PowerShellHostName = '' -# Minimum version of the PowerShell host required by this module -# PowerShellHostVersion = '' + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' -# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# DotNetFrameworkVersion = '' + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' -# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. -# ClrVersion = '' + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' -# Processor architecture (None, X86, Amd64) required by this module -# ProcessorArchitecture = '' + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' -# Modules that must be imported into the global environment prior to importing this module -RequiredModules = @(@{ModuleName = 'Microsoft.Graph.Authentication'; GUID = '883916f2-9184-46ee-b1f8-b6a2fb784cee'; ModuleVersion = '2.2.0'; }) + # Modules that must be imported into the global environment prior to importing this module + RequiredModules = @(@{ModuleName = 'Microsoft.Graph.Authentication'; GUID = '883916f2-9184-46ee-b1f8-b6a2fb784cee'; ModuleVersion = '2.2.0'; }) -# Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() -# Script files (.ps1) that are run in the caller's environment prior to importing this module. -# ScriptsToProcess = @() + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() -# Type files (.ps1xml) to be loaded when importing this module -# TypesToProcess = @() + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() -# Format files (.ps1xml) to be loaded when importing this module -# FormatsToProcess = @() + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() -# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -NestedModules = @() + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + NestedModules = @() -# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = 'Clear-MtGraphCache', 'Get-MtConditionalAccessPolicies', - 'Invoke-MtGraphRequest', 'Test-MtAppManagementPolicyEnabled', - 'Test-MtCaAllAppsExists', 'Test-MtCaDeviceComplianceExists', - 'Test-MtCaEmergencyAccessExists', 'Test-MtCaMfaForAdmins', - 'Test-MtCaMfaForAllUsers', 'Test-MtConditionalAccessWhatIf' + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = 'Clear-MtGraphCache', 'Get-MtConditionalAccessPolicies', + 'Invoke-MtGraphRequest', 'Test-MtAppManagementPolicyEnabled', + 'Test-MtCaAllAppsExists', 'Test-MtCaDeviceComplianceExists', + 'Test-MtCaEmergencyAccessExists', 'Test-MtCaMfaForAdmins', + 'Test-MtCaMfaForAllUsers', 'Test-MtConditionalAccessWhatIf', + 'Test-MtCaMfaForAdminManagement', 'Test-MtCaBlockLegacyOtherAuthentication', + 'Test-MtCaBlockLegacyExchangeActiveSyncAuthentication', 'Test-MtCaSecureSecurityInfoRegistration', + 'Test-MtCaMfaForRiskySignIns', 'Test-MtCaRequirePasswordChangeForHighUserRisk', + 'Test-MtCaDeviceComplianceAdminsExists', 'Test-MtCaBlockUnknownOrUnsupportedDevicePlatforms', + 'Test-MtCaMfaForGuests', 'Test-MtCaEnforceNonPersistentBrowserSession', + 'Test-MtCaEnforceSignInFrequency', 'Test-MtCaApplicationEnforcedRestrictions' -# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. -CmdletsToExport = @() + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + CmdletsToExport = @() -# Variables to export from this module -# VariablesToExport = @() + # Variables to export from this module + # VariablesToExport = @() -# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. -AliasesToExport = @() + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + AliasesToExport = @() -# DSC resources to export from this module -# DscResourcesToExport = @() + # DSC resources to export from this module + # DscResourcesToExport = @() -# List of all modules packaged with this module -# ModuleList = @() + # List of all modules packaged with this module + # ModuleList = @() -# List of all files packaged with this module -# FileList = @() + # List of all files packaged with this module + # FileList = @() -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ - PSData = @{ + PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. - Tags = 'Microsoft','365','Cloud','Test','Pester','Entra','AzureAD','Automation','Regression','DevOps' + # Tags applied to this module. These help with module discovery in online galleries. + Tags = 'Microsoft', '365', 'Cloud', 'Test', 'Pester', 'Entra', 'AzureAD', 'Automation', 'Regression', 'DevOps' - # A URL to the license for this module. - LicenseUri = 'https://raw.githubusercontent.com/microsoft/maester/main/LICENSE' + # A URL to the license for this module. + LicenseUri = 'https://raw.githubusercontent.com/microsoft/maester/main/LICENSE' - # A URL to the main website for this project. - ProjectUri = 'https://github.com/microsoft/maester' + # A URL to the main website for this project. + ProjectUri = 'https://github.com/microsoft/maester' - # A URL to an icon representing this module. - # IconUri = '' + # A URL to an icon representing this module. + # IconUri = '' - # ReleaseNotes of this module - # ReleaseNotes = '' + # ReleaseNotes of this module + # ReleaseNotes = '' - # Prerelease string of this module - # Prerelease = '' + # Prerelease string of this module + # Prerelease = '' - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false - # External dependent modules of this module - # ExternalModuleDependencies = @() + # External dependent modules of this module + # ExternalModuleDependencies = @() - } # End of PSData hashtable + } # End of PSData hashtable - } # End of PrivateData hashtable + } # End of PrivateData hashtable -# HelpInfo URI of this module -# HelpInfoURI = '' + # HelpInfo URI of this module + # HelpInfoURI = '' -# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. -# DefaultCommandPrefix = '' + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' } diff --git a/src/public/Test-MtCaAllAppsExists.ps1 b/src/public/Test-MtCaAllAppsExists.ps1 index 45e92333..7ffefd23 100644 --- a/src/public/Test-MtCaAllAppsExists.ps1 +++ b/src/public/Test-MtCaAllAppsExists.ps1 @@ -7,7 +7,7 @@ and ideally should be enabled for all users. Learn more: - https://learn.microsoft.com/en-us/entra/identity/conditional-access/plan-conditional-access#apply-conditional-access-policies-to-every-app + https://learn.microsoft.com/entra/identity/conditional-access/plan-conditional-access#apply-conditional-access-policies-to-every-app .Example Test-MtCaAllAppsExists @@ -24,18 +24,20 @@ Function Test-MtCaAllAppsExists { [switch] $SkipCheckAllUsers = $false ) - $policies = Get-MtConditionalAccessPolicies - Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } $result = $false foreach ($policy in $policies) { - if ($policy.conditions.applications.includeApplications -eq 'all' ` - -and $policy.state -eq 'enabled') { - - $result = $SkipCheckAllUsers.IsPresent ` - -or $policy.conditions.users.includeusers -eq 'all' + if ( ( $SkipCheckAllUsers.IsPresent -or $policy.conditions.users.includeUsers -eq "All" ) ` + -and $policy.conditions.applications.includeApplications -eq 'all' ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false } + Write-Verbose "$($policy.displayName) - $currentresult" } return $result diff --git a/src/public/Test-MtCaApplicationEnforcedRestrictions.ps1 b/src/public/Test-MtCaApplicationEnforcedRestrictions.ps1 new file mode 100644 index 00000000..f3ac89a8 --- /dev/null +++ b/src/public/Test-MtCaApplicationEnforcedRestrictions.ps1 @@ -0,0 +1,40 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy is configured to enable application enforced restrictions + + .Description + Application enforced restrictions conditional access policy can be helpful to minimize the risk of data leakage from a shared device. + + Learn more: + https://aka.ms/CATemplatesAppRestrictions + + .Example + Test-MtCaApplicationEnforcedRestrictions +#> + +Function Test-MtCaApplicationEnforcedRestrictions { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + + $result = $false + foreach ($policy in $policies) { + if ( $policy.conditions.users.includeUsers -eq "All" ` + -and $policy.conditions.clientAppTypes -eq "All" ` + -and $policy.sessionControls.applicationEnforcedRestrictions.isEnabled -eq $true ` + -and "Office365" -in $policy.conditions.applications.includeApplications ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaBlockLegacyExchangeActiveSyncAuthentication.ps1 b/src/public/Test-MtCaBlockLegacyExchangeActiveSyncAuthentication.ps1 new file mode 100644 index 00000000..0b3fc721 --- /dev/null +++ b/src/public/Test-MtCaBlockLegacyExchangeActiveSyncAuthentication.ps1 @@ -0,0 +1,41 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy that blocks legacy authentication for Exchange Active Sync authentication. + + .Description + Legacy authentication is an unsecure method to authenticate. This function checks if the tenant has at least one + conditional access policy that blocks legacy authentication. + + Learn more: + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-block-legacy + + .Example + Test-MtCaBlockLegacyExchangeActiveSyncAuthentication +#> + +Function Test-MtCaBlockLegacyExchangeActiveSyncAuthentication { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + + $result = $false + foreach ($policy in $policies) { + if ( $policy.grantcontrols.builtincontrols -contains 'block' ` + -and "exchangeActiveSync" -in $policy.conditions.clientAppTypes ` + -and $policy.conditions.applications.includeApplications -eq "00000002-0000-0ff1-ce00-000000000000" ` + -and $policy.conditions.users.includeUsers -eq "All" ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaBlockLegacyOtherAuthentication.ps1 b/src/public/Test-MtCaBlockLegacyOtherAuthentication.ps1 new file mode 100644 index 00000000..ec74b4ee --- /dev/null +++ b/src/public/Test-MtCaBlockLegacyOtherAuthentication.ps1 @@ -0,0 +1,43 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy that blocks legacy authentication. + + .Description + Legacy authentication is an unsecure method to authenticate. This function checks if the tenant has at least one + conditional access policy that blocks legacy authentication. + + Learn more: + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-block-legacy + + .Example + Test-MtCaBlockLegacyOtherAuthentication +#> + +Function Test-MtCaBlockLegacyOtherAuthentication { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + # Remove policies that require password change, as they are related to user risk and not MFA on signin + $policies = $policies | Where-Object { $_.grantcontrols.builtincontrols -notcontains 'passwordChange' } + + $result = $false + foreach ($policy in $policies) { + if ( $policy.grantcontrols.builtincontrols -contains 'block' ` + -and "other" -in $policy.conditions.clientAppTypes ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + -and $policy.conditions.users.includeUsers -eq "All" ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaBlockUnknownOrUnsupportedDevicePlatforms.ps1 b/src/public/Test-MtCaBlockUnknownOrUnsupportedDevicePlatforms.ps1 new file mode 100644 index 00000000..ff545b8a --- /dev/null +++ b/src/public/Test-MtCaBlockUnknownOrUnsupportedDevicePlatforms.ps1 @@ -0,0 +1,54 @@ +<# + .Synopsis + Checks if the tenant has at least one Conditional Access policy is configured to block access for unknown or unsupported device platforms + + .Description + Microsoft recommends blocking access for unknown or unsupported device platforms. + + Learn more: + https://learn.microsoft.com/entra/identity/conditional-access/concept-conditional-access-policy-common + + .Example + Test-MtCaBlockUnknownOrUnsupportedDevicePlatforms +#> + +Function Test-MtCaBlockUnknownOrUnsupportedDevicePlatforms { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + + $KnownPlatforms = @( + "android", + "iOS", + "windows", + "macOS", + "linux", + "windowsPhone" + ) + + $result = $false + foreach ($policy in $policies) { + try { + # Check if all known platforms are excluded from the policy + $AllKnownPlatformsExcluded = ( Compare-Object -ReferenceObject $KnownPlatforms -DifferenceObject $policy.conditions.platforms.excludePlatforms -IncludeEqual -ExcludeDifferent -PassThru | Measure-Object | Select-Object -ExpandProperty Count ) -eq $KnownPlatforms.Count + } catch { + $AllKnownPlatformsExcluded = $false + } + if ( $policy.grantcontrols.builtincontrols -eq 'block' ` + -and $policy.conditions.platforms.includePlatforms -eq "All" ` + -and $AllKnownPlatformsExcluded -ne $false ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaDeviceComplianceAdminsExists.ps1 b/src/public/Test-MtCaDeviceComplianceAdminsExists.ps1 new file mode 100644 index 00000000..4ded21a8 --- /dev/null +++ b/src/public/Test-MtCaDeviceComplianceAdminsExists.ps1 @@ -0,0 +1,61 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy requiring device compliance for admins. + + .Description + Device compliance conditional access policy can be used to require devices to be compliant or hybrid Azure AD joined for admins. + This is a good way to prevent AITM attacks. + + Learn more: + https://aka.ms/CATemplatesAdminDevices + + .Example + Test-MtCaDeviceComplianceAdminsExists +#> + +Function Test-MtCaDeviceComplianceAdminsExists { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $AdministrativeRolesToCheck = @( + "62e90394-69f5-4237-9190-012177145e10", + "194ae4cb-b126-40b2-bd5b-6091b380977d", + "f28a1f50-f6e7-4571-818b-6a12f2af6b6c", + "29232cdf-9323-42fd-ade2-1d097af3e4de", + "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9", + "729827e3-9c14-49f7-bb1b-9608f156bbb8", + "b0f54661-2d74-4c50-afa3-1ec803f12efe", + "fe930be7-5e62-47db-91af-98c3a49a38b1", + "c4e39bd9-1100-46d3-8c65-fb160da0071f", + "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3", + "158c047a-c907-4556-b7ef-446551a6b5f7", + "966707d0-3269-4727-9be2-8c3a10f19b9d", + "7be44c8a-adaf-4e2a-84d6-ab2649e08a13", + "e8611ab8-c189-46e8-94e1-60213ab1f814" + ) + + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + + $result = $false + foreach ($policy in $policies) { + $PolicyIncludesAllRoles = $true + $AdministrativeRolesToCheck | ForEach-Object { + if ( $_ -notin $policy.conditions.users.includeRoles ) { + $PolicyIncludesAllRoles = $false + } + } + if ( 'domainJoinedDevice' -in $policy.grantcontrols.builtincontrols ` + -and 'compliantDevice' -in $policy.grantcontrols.builtincontrols ` + -and $policy.grantControls.operator -eq "OR" ` + -and $PolicyIncludesAllRoles ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + ) { + $result = $true + } + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaDeviceComplianceExists.ps1 b/src/public/Test-MtCaDeviceComplianceExists.ps1 index 7171a8df..a553b8d3 100644 --- a/src/public/Test-MtCaDeviceComplianceExists.ps1 +++ b/src/public/Test-MtCaDeviceComplianceExists.ps1 @@ -6,7 +6,7 @@ Device compliance conditional access policy can be used to require devices to be compliant with the tenant's device compliance policy. Learn more: - https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-compliant-device + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-compliant-device .Example Test-MtCaDeviceComplianceExists @@ -17,13 +17,14 @@ Function Test-MtCaDeviceComplianceExists { [OutputType([bool])] param () - $policies = Get-MtConditionalAccessPolicies - Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies $result = $false foreach ($policy in $policies) { - if ($policy.grantcontrols.builtincontrols -contains 'compliantDevice' -and $policy.state -eq 'enabled') { + if ($policy.grantcontrols.builtincontrols -contains 'compliantDevice' ` + -and $policy.state -eq 'enabled' ` + ) { $result = $true } } diff --git a/src/public/Test-MtCaEmergencyAccessExists.ps1 b/src/public/Test-MtCaEmergencyAccessExists.ps1 index 80770a04..9d92324d 100644 --- a/src/public/Test-MtCaEmergencyAccessExists.ps1 +++ b/src/public/Test-MtCaEmergencyAccessExists.ps1 @@ -1,13 +1,13 @@ <# .Synopsis - Checks if the tenant has at least one emergency account or account group excluded from all conditional access policies + Checks if the tenant has at least one emergency/break glass account or account group excluded from all conditional access policies .Description - It is recommended to have at least one emergency account or account group excluded from all conditional access policies. + It is recommended to have at least one emergency/break glass account or account group excluded from all conditional access policies. This allows for emergency access to the tenant in case of a misconfiguration or other issues. Learn more: - https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access + https://learn.microsoft.com/entra/identity/role-based-access-control/security-emergency-access .Example Test-MtCaEmergencyAccessExists @@ -18,17 +18,27 @@ Function Test-MtCaEmergencyAccessExists { [OutputType([bool])] param () - $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } - Set-StrictMode -Off + # Only check policies that are not related to authentication context + $policies = Get-MtConditionalAccessPolicies | Where-Object { -not $_.conditions.applications.includeAuthenticationContextClassReferences } $result = $false $PolicyCount = $policies | Measure-Object | Select-Object -ExpandProperty Count + $ExcludedUserObjectGUID = $policies.conditions.users.excludeUsers | Group-Object -NoElement | Sort-Object -Property Count -Descending | Select-Object -First 1 -ExpandProperty Name $ExcludedUsers = $policies.conditions.users.excludeUsers | Group-Object -NoElement | Sort-Object -Property Count -Descending | Select-Object -First 1 | Select-Object -ExpandProperty Count + $ExcludedGroupObjectGUID = $policies.conditions.users.excludeGroups | Group-Object -NoElement | Sort-Object -Property Count -Descending | Select-Object -First 1 -ExpandProperty Name $ExcludedGroups = $policies.conditions.users.excludeGroups | Group-Object -NoElement | Sort-Object -Property Count -Descending | Select-Object -First 1 | Select-Object -ExpandProperty Count # If the number of enabled policies is not the same as the number of excluded users or groups, there is no emergency access if ($PolicyCount -eq $ExcludedUsers -or $PolicyCount -eq $ExcludedGroups) { $result = $true + } else { + # If the number of excluded users is higher than the number of excluded groups, check the user object GUID + $CheckId = $ExcludedUsers -gt $ExcludedGroups ? $ExcludedUserObjectGUID : $ExcludedGroupObjectGUID + Write-Verbose "Emergency access account or group: $CheckId" + # Check if the emergency access account or group is excluded from all policies and write verbose output + $policies | Where-Object { $CheckId -notin $_.conditions.users.excludeUsers -and $CheckId -notin $_.conditions.users.excludeGroups } | Select-Object -ExpandProperty displayName | Sort-Object | ForEach-Object { + Write-Warning "Conditional Access policy $_ does not exclude emergency access account or group" + } } Set-StrictMode -Version Latest diff --git a/src/public/Test-MtCaEnforceNonPersistentBrowserSession.ps1 b/src/public/Test-MtCaEnforceNonPersistentBrowserSession.ps1 new file mode 100644 index 00000000..6216be57 --- /dev/null +++ b/src/public/Test-MtCaEnforceNonPersistentBrowserSession.ps1 @@ -0,0 +1,65 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy enforcing non persistent browser session + + .Description + Non persistent browser session conditional access policy can be helpful to minimize the risk of data leakage from a shared device. + + Learn more: + https://aka.ms/CATemplatesBrowserSession + + .Example + Test-MtCaEnforceNonPersistentBrowserSession +#> + +Function Test-MtCaEnforceNonPersistentBrowserSession { + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter()] + [switch]$AllDevices + ) + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + + $result = $false + foreach ($policy in $policies) { + if (-not $AllDevices.IsPresent) { + # Check if device filter for compliant or hybrid Azure AD joined devices is present + if ( $policy.conditions.devices.deviceFilter.mode -eq "include" ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.trustType -ne \"ServerAD\"' ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.isCompliant -ne True' ` + ) { + $IsDeviceFilterPresent = $true + } elseif ( $policy.conditions.devices.deviceFilter.mode -eq "exclude" ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.trustType -eq \"ServerAD\"' ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.isCompliant -eq True' ` + ) { + $IsDeviceFilterPresent = $true + } else { + $IsDeviceFilterPresent = $false + } + } else { + Write-Verbose "All devices are selected" + # We don't care about device filter if we are checking for all devices + $IsDeviceFilterPresent = $true + } + + if ( $policy.sessionControls.persistentBrowser.isEnabled -eq $true ` + -and $policy.sessionControls.persistentBrowser.mode -eq "never" ` + -and $IsDeviceFilterPresent ` + -and $policy.conditions.users.includeUsers -eq "All" ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaEnforceSignInFrequency.ps1 b/src/public/Test-MtCaEnforceSignInFrequency.ps1 new file mode 100644 index 00000000..c5565e42 --- /dev/null +++ b/src/public/Test-MtCaEnforceSignInFrequency.ps1 @@ -0,0 +1,63 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy enforcing sign-in frequency for non-corporate devices + + .Description + Sign-in frequency conditional access policy can be helpful to minimize the risk of data leakage from a shared device. + + Learn more: + https://aka.ms/CATemplatesBrowserSession + + .Example + Test-MtCaEnforceSignInFrequency +#> + +Function Test-MtCaEnforceSignInFrequency { + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter()] + [switch]$AllDevices + ) + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + + $result = $false + foreach ($policy in $policies) { + # Check if device filter for compliant or hybrid Azure AD joined devices is present + if (-not $AllDevices.IsPresent) { + if ( $policy.conditions.devices.deviceFilter.mode -eq "include" ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.trustType -ne \"ServerAD\"' ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.isCompliant -ne True' ` + ) { + $IsDeviceFilterPresent = $true + } elseif ( $policy.conditions.devices.deviceFilter.mode -eq "exclude" ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.trustType -eq \"ServerAD\"' ` + -and $policy.conditions.devices.deviceFilter.rule -match 'device.isCompliant -eq True' ` + ) { + $IsDeviceFilterPresent = $true + } else { + $IsDeviceFilterPresent = $false + } + } else { + # We don't care about device filter if we are checking for all devices + $IsDeviceFilterPresent = $true + } + if ( $policy.sessionControls.signInFrequency.isEnabled -eq $true ` + -and $policy.sessionControls.signInFrequency.frequencyInterval -eq "timeBased" ` + -and $IsDeviceFilterPresent ` + -and $policy.conditions.users.includeUsers -eq "All" ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaMfaForAdminManagement.ps1 b/src/public/Test-MtCaMfaForAdminManagement.ps1 new file mode 100644 index 00000000..799f52c4 --- /dev/null +++ b/src/public/Test-MtCaMfaForAdminManagement.ps1 @@ -0,0 +1,41 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy requiring multifactor authentication to access Azure management. + + .Description + MFA for Azure management is a critical security control. This function checks if the tenant has at least one + conditional access policy requiring multifactor authentication to access Azure management. + + Learn more: + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-azure-management + + .Example + Test-MtCaMfaForAdminManagement +#> + +Function Test-MtCaMfaForAdminManagement { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + + $result = $false + foreach ($policy in $policies) { + if ( ( $policy.grantcontrols.builtincontrols -contains 'mfa' ` + -or $policy.grantcontrols.authenticationStrength.requirementsSatisfied -contains 'mfa' ) ` + -and $policy.conditions.users.includeUsers -eq "All" ` + -and "797f4846-ba00-4fd7-ba43-dac1f8f63013" -in $policy.conditions.applications.includeApplications ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaMfaForAdmins.ps1 b/src/public/Test-MtCaMfaForAdmins.ps1 index 7c6492ac..1349de54 100644 --- a/src/public/Test-MtCaMfaForAdmins.ps1 +++ b/src/public/Test-MtCaMfaForAdmins.ps1 @@ -6,7 +6,7 @@ MFA for admins conditional access policy can be used to require MFA for all admins in the tenant. Learn more: - https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-admin-mfa + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-admin-mfa .Example Test-MtCaMfaForAdmins @@ -34,9 +34,8 @@ Function Test-MtCaMfaForAdmins { "e8611ab8-c189-46e8-94e1-60213ab1f814" ) - $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } - Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } $result = $false foreach ($policy in $policies) { @@ -46,7 +45,11 @@ Function Test-MtCaMfaForAdmins { $PolicyIncludesAllRoles = $false } } - if ( ( $policy.grantcontrols.builtincontrols -contains 'mfa' -or $policy.grantcontrols.authenticationStrength.requirementsSatisfied -contains 'mfa' ) -and $PolicyIncludesAllRoles -and $policy.conditions.applications.includeApplications -eq "All" ) { + if ( ( $policy.grantcontrols.builtincontrols -contains 'mfa' ` + -or $policy.grantcontrols.authenticationStrength.requirementsSatisfied -contains 'mfa' ) ` + -and $PolicyIncludesAllRoles ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + ) { $result = $true $currentresult = $true } else { diff --git a/src/public/Test-MtCaMfaForAllUsers.ps1 b/src/public/Test-MtCaMfaForAllUsers.ps1 index 6d9851ba..e4662353 100644 --- a/src/public/Test-MtCaMfaForAllUsers.ps1 +++ b/src/public/Test-MtCaMfaForAllUsers.ps1 @@ -6,7 +6,7 @@ MFA for all users conditional access policy can be used to require MFA for all users in the tenant. Learn more: - https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-all-users-mfa + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-all-users-mfa .Example Test-MtCaMfaForAllUsers @@ -17,15 +17,18 @@ Function Test-MtCaMfaForAllUsers { [OutputType([bool])] param () + Set-StrictMode -Off $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } # Remove policies that require password change, as they are related to user risk and not MFA on signin $policies = $policies | Where-Object { $_.grantcontrols.builtincontrols -notcontains 'passwordChange' } - Set-StrictMode -Off - $result = $false foreach ($policy in $policies) { - if ( ( $policy.grantcontrols.builtincontrols -contains 'mfa' -or $policy.grantcontrols.authenticationStrength.requirementsSatisfied -contains 'mfa' ) -and $policy.conditions.users.includeUsers -eq "All" -and $policy.conditions.applications.includeApplications -eq "All" ) { + if ( ( $policy.grantcontrols.builtincontrols -contains 'mfa' ` + -or $policy.grantcontrols.authenticationStrength.requirementsSatisfied -contains 'mfa' ) ` + -and $policy.conditions.users.includeUsers -eq "All" ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + ) { $result = $true $currentresult = $true } else { diff --git a/src/public/Test-MtCaMfaForGuests.ps1 b/src/public/Test-MtCaMfaForGuests.ps1 new file mode 100644 index 00000000..993b7b14 --- /dev/null +++ b/src/public/Test-MtCaMfaForGuests.ps1 @@ -0,0 +1,51 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy requiring multifactor authentication for all guest users. + + .Description + MFA for all users conditional access policy can be used to require MFA for all guest users in the tenant. + + Learn more: + https://aka.ms/CATemplatesGuest + + .Example + Test-MtCaMfaForGuests +#> + +Function Test-MtCaMfaForGuests { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + # Remove policies that require password change, as they are related to user risk and not MFA on signin + $policies = $policies | Where-Object { $_.grantcontrols.builtincontrols -notcontains 'passwordChange' } + + $GuestTypes = @( "internalGuest", "b2bCollaborationGuest", "b2bCollaborationMember", "b2bDirectConnectUser", "otherExternalUser", "serviceProvider" ) + + $result = $false + foreach ($policy in $policies) { + try { + # Check if all guest types are present in the policy and compare with the known count of the guest types + $AllGuestTypesPresent = ( Compare-Object -ReferenceObject $GuestTypes -DifferenceObject ( $policy.conditions.users.includeGuestsOrExternalUsers.guestOrExternalUserTypes -split ',') -IncludeEqual -ExcludeDifferent -PassThru | Measure-Object | Select-Object -ExpandProperty Count ) -eq $GuestTypes.Count + } catch { + $AllGuestTypesPresent = $false + } + if ( ( $policy.grantcontrols.builtincontrols -contains 'mfa' ` + -or $policy.grantcontrols.authenticationStrength.requirementsSatisfied -contains 'mfa' ) ` + -and ( $policy.conditions.users.includeUsers -eq "GuestsOrExternalUsers" ` + -or $AllGuestTypesPresent ) ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaMfaForRiskySignIns.ps1 b/src/public/Test-MtCaMfaForRiskySignIns.ps1 new file mode 100644 index 00000000..80c40e49 --- /dev/null +++ b/src/public/Test-MtCaMfaForRiskySignIns.ps1 @@ -0,0 +1,43 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy requiring multifactor authentication for risky sign-ins. + + .Description + MFA for risky sign-ins conditional access policy can be used to require MFA for all users in the tenant. + + Learn more: + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-risk + + .Example + Test-MtCaMfaForRiskySignIns +#> + +Function Test-MtCaMfaForRiskySignIns { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + # Remove policies that require password change, as they are related to user risk and not MFA on signin + $policies = $policies | Where-Object { $_.grantcontrols.builtincontrols -notcontains 'passwordChange' } + + $result = $false + foreach ($policy in $policies) { + if ( ( $policy.grantcontrols.builtincontrols -contains 'mfa' ` + -or $policy.grantcontrols.authenticationStrength.requirementsSatisfied -contains 'mfa' ) ` + -and $policy.conditions.users.includeUsers -eq "All" ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + -and "high" -in $policy.conditions.signInRiskLevels ` + -and "medium" -in $policy.conditions.signInRiskLevels ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaRequirePasswordChangeForHighUserRisk.ps1 b/src/public/Test-MtCaRequirePasswordChangeForHighUserRisk.ps1 new file mode 100644 index 00000000..01147fca --- /dev/null +++ b/src/public/Test-MtCaRequirePasswordChangeForHighUserRisk.ps1 @@ -0,0 +1,42 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy requiring password change for high user risk. + + .Description + Password change for high user risk is a good way to prevent compromised accounts from being used to access your tenant. + + Learn more: + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-risk-user + + .Example + Test-MtCaRequirePasswordChangeForHighUserRisk +#> + +Function Test-MtCaRequirePasswordChangeForHighUserRisk { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + # Only check policies that have password change as a grant control + $policies = $policies | Where-Object { $_.grantcontrols.builtincontrols -contains 'passwordChange' } + + $result = $false + foreach ($policy in $policies) { + if ( $policy.grantcontrols.builtincontrols -contains 'passwordChange' ` + -and $policy.conditions.users.includeUsers -eq "All" ` + -and $policy.conditions.applications.includeApplications -eq "All" ` + -and "high" -in $policy.conditions.userRiskLevels ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/src/public/Test-MtCaSecureSecurityInfoRegistration.ps1 b/src/public/Test-MtCaSecureSecurityInfoRegistration.ps1 new file mode 100644 index 00000000..8f6b2dde --- /dev/null +++ b/src/public/Test-MtCaSecureSecurityInfoRegistration.ps1 @@ -0,0 +1,43 @@ +<# + .Synopsis + Checks if the tenant has at least one conditional access policy securing security info registration. + + .Description + Security info registration conditional access policy can secure the registration of security info for users in the tenant. + + Learn more: + https://learn.microsoft.com/entra/identity/conditional-access/howto-conditional-access-policy-registration + + .Example + Test-MtCaSecureSecurityInfoRegistration +#> + +Function Test-MtCaSecureSecurityInfoRegistration { + [CmdletBinding()] + [OutputType([bool])] + param () + + Set-StrictMode -Off + $policies = Get-MtConditionalAccessPolicies | Where-Object { $_.state -eq "enabled" } + # Remove policies that require password change, as they are related to user risk and not MFA on signin + $policies = $policies | Where-Object { $_.grantcontrols.builtincontrols -notcontains 'passwordChange' } + + $result = $false + foreach ($policy in $policies) { + if ( $policy.conditions.users.includeUsers -eq "All" ` + -and $policy.conditions.clientAppTypes -eq "all" ` + -and $policy.conditions.applications.includeUserActions -eq "urn:user:registersecurityinfo" ` + -and $policy.conditions.users.excludeRoles -eq "62e90394-69f5-4237-9190-012177145e10" ` + -and $policy.conditions.locations.excludeLocations -eq "AllTrusted" ` + ) { + $result = $true + $currentresult = $true + } else { + $currentresult = $false + } + Write-Verbose "$($policy.displayName) - $currentresult" + } + Set-StrictMode -Version Latest + + return $result +} \ No newline at end of file diff --git a/tests/Identity/Test-ConditionalAccessBaseline.Tests.ps1 b/tests/Identity/Test-ConditionalAccessBaseline.Tests.ps1 index 1ad45b1e..88c492de 100644 --- a/tests/Identity/Test-ConditionalAccessBaseline.Tests.ps1 +++ b/tests/Identity/Test-ConditionalAccessBaseline.Tests.ps1 @@ -9,7 +9,7 @@ Describe "Conditional Access Baseline Policies" -Tag "CA", "Security", "All" { It "ID1004: At least one Conditional Access policy is configured with All Apps and All Users. See https://maester.dev/t/ID1004" { Test-MtCaAllAppsExists | Should -Be $true -Because "There is no policy scoped to All Apps and All Users" } - It "ID1005: All Conditional Access policies are configured to exclude at least one emergency account or group. See https://maester.dev/t/ID1005" { + It "ID1005: All Conditional Access policies are configured to exclude at least one emergency/break glass account or group. See https://maester.dev/t/ID1005" { Test-MtCaEmergencyAccessExists | Should -Be $true -Because "There is no emergency access account or group present in all enabled policies" } It "ID1006: At least one Conditional Access policy is configured to require MFA for admins. See https://maester.dev/t/ID1006" { @@ -18,4 +18,40 @@ Describe "Conditional Access Baseline Policies" -Tag "CA", "Security", "All" { It "ID1007: At least one Conditional Access policy is configured to require MFA for all users. See https://maester.dev/t/ID1007" { Test-MtCaMfaForAllUsers | Should -Be $true -Because "There is no policy that requires MFA for all users" } + It "ID1008: At least one Conditional Access policy is configured to require MFA for Azure management. See https://maester.dev/t/ID1008" { + Test-MtCaMfaForAdminManagement | Should -Be $true -Because "There is no policy that requires MFA for Azure management" + } + It "ID1009: At least one Conditional Access policy is configured to block other legacy authentication. See https://maester.dev/t/ID1009" { + Test-MtCaBlockLegacyOtherAuthentication | Should -Be $true -Because "There is no policy that blocks legacy authentication" + } + It "ID1010: At least one Conditional Access policy is configured to block legacy authentication for Exchange ActiveSync. See https://maester.dev/t/ID1010" { + Test-MtCaBlockLegacyExchangeActiveSyncAuthentication | Should -Be $true -Because "There is no policy that blocks legacy authentication for Exchange ActiveSync" + } + It "ID1011: At least one Conditional Access policy is configured to secure security info registration only from a trusted location. See https://maester.dev/t/ID1011" { + Test-MtCaSecureSecurityInfoRegistration | Should -Be $true -Because "There is no policy that secures security info registration" + } + It "ID1012: At least one Conditional Access policy is configured to require MFA for risky sign-ins. See https://maester.dev/t/ID1012" { + Test-MtCaMfaForRiskySignIns | Should -Be $true -Because "There is no policy that requires MFA for risky sign-ins" + } + It "ID1013: At least one Conditional Access policy is configured to require new password when user risk is high. See https://maester.dev/t/ID1013" { + Test-MtCaRequirePasswordChangeForHighUserRisk | Should -Be $true -Because "There is no policy that requires new password when user risk is high" + } + It "ID1014: At least one Conditional Access policy is configured to require compliant or hybrid Azure AD joined devices for admins. See https://maester.dev/t/ID1014" { + Test-MtCaDeviceComplianceAdminsExists | Should -Be $true -Because "There is no policy that requires compliant or hybrid Azure AD joined devices for admins" + } + It "ID1015: At least one Conditional Access policy is configured to block access for unknown or unsupported device platforms. See https://maester.dev/t/ID1015" { + Test-MtCaBlockUnknownOrUnsupportedDevicePlatforms | Should -Be $true -Because "There is no policy that blocks access for unknown or unsupported device platforms" + } + It "ID1016: At least one Conditional Access policy is configured to require MFA for guest access. See https://maester.dev/t/ID1016" { + Test-MtCaMfaForGuests | Should -Be $true -Because "There is no policy that requires MFA for guest access" + } + It "ID1017: At least one Conditional Access policy is configured to enforce non persistent browser session for non-corporate devices. See https://maester.dev/t/ID1017" { + Test-MtCaEnforceNonPersistentBrowserSession | Should -Be $true -Because "There is no policy that enforces non persistent browser session for non-corporate devices" + } + It "ID1018: At least one Conditional Access policy is configured to enforce sign-in frequency for non-corporate devices. See https://maester.dev/t/ID1018" { + Test-MtCaEnforceSignInFrequency | Should -Be $true -Because "There is no policy that enforces sign-in frequency for non-corporate devices" + } + It "ID1019: At least one Conditional Access policy is configured to enable application enforced restrictions. See https://maester.dev/t/ID1019" { + Test-MtCaApplicationEnforcedRestrictions | Should -Be $true -Because "There is no policy that enables application enforced restrictions" + } } diff --git a/tests/Invoke-Maester.ps1 b/tests/Invoke-Maester.ps1 index 77a022e7..7e10df03 100644 --- a/tests/Invoke-Maester.ps1 +++ b/tests/Invoke-Maester.ps1 @@ -5,10 +5,10 @@ $motd = @" .___ ___. ___ _______ _______.___________. _______ .______ ____ ____ ___ __ | \/ | / \ | ____| / | || ____|| _ \ \ \ / / / _ \ /_ | -| \ / | / ^ \ | |__ | (----`---| |----`| |__ | |_) | \ \/ / | | | | | | +| \ / | / ^ \ | |__ | (--------| |----``| |__ | |_) | \ \/ / | | | | | | | |\/| | / /_\ \ | __| \ \ | | | __| | / \ / | | | | | | | | | | / _____ \ | |____.----) | | | | |____ | |\ \----. \ / | |_| | __| | -|__| |__| /__/ \__\ |_______|_______/ |__| |_______|| _| `._____| \__/ \___/ (__)_| +|__| |__| /__/ \__\ |_______|_______/ |__| |_______|| _| ``._____| \__/ \___/ (__)_| "@ @@ -20,42 +20,20 @@ $RequiredScopes = @( 'Policy.ReadWrite.ConditionalAccess' ) $CurrentScopes = Get-MgContext | Select-Object -ExpandProperty Scopes -$RequiredScopesOkay = [bool][string]::IsNullOrWhiteSpace( $( Compare-Object -ReferenceObject $CurrentScopes -DifferenceObject $RequiredScopes | Where-Object { $_.SideIndicator -eq '=>' } | Select-Object -ExpandProperty InputObject ) ) +try { + $RequiredScopesOkay = [bool][string]::IsNullOrWhiteSpace( $( Compare-Object -ReferenceObject $CurrentScopes -DifferenceObject $RequiredScopes | Where-Object { $_.SideIndicator -eq '=>' } | Select-Object -ExpandProperty InputObject ) ) +} catch { + $RequiredScopesOkay = $false +} if ( -not $RequiredScopesOkay ) { - Connect-MgGraph -UseDeviceAuthentication -Scope $RequiredScopes + Connect-MgGraph -UseDeviceAuthentication -Scope $RequiredScopes -NoWelcome } $PSDefaultParameterValues = @{ 'Invoke-MgGraphRequest:Verbose' = $false } -#region Define variables to match your environment - -$BreakGlassUserIds = @("a5032592-a8c0-4b9c-9b62-0d137cfede11") - -# Create a hash table with Azure AD users to test, you can change the number of users to test -$TestNumberOfUsers = 1 -$TestAzureADUser = New-Object -TypeName 'System.Collections.ArrayList' -$AADUsers = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users?`$top=$($TestNumberOfUsers+10)" | Select-Object -ExpandProperty value | Select-Object id, userPrincipalName -# Filter out the break glass users and limit it to $TestNumberOfUsers -$AADUsers = $AADUsers | Where-Object { $_.id -notcontains $BreakGlassUserIds } | Select-Object -First $TestNumberOfUsers -foreach ($User in $AADUsers) { - $TempHashTable = @{ - userId = $User.id - userPrincipalName = $User.userPrincipalName - } - $TestAzureADUser.Add($TempHashTable) | Out-Null -} -#endregion - -$configRunContainer = @( - # Add global variables to the container - New-PesterContainer -Path "*.Tests.ps1" -Data @{ - AzureADUser = $TestAzureADUser - BreakGlassUserIds = $BreakGlassUserIds - } -) $PesterConfiguration = New-PesterConfiguration -Hashtable @{ Filter = @{ @@ -66,8 +44,7 @@ $PesterConfiguration = New-PesterConfiguration -Hashtable @{ Enabled = $true } Run = @{ - Exit = $true - Container = $configRunContainer + Exit = $true } Should = @{ ErrorAction = 'Continue'