At work, me and my collagues frequently review Azure AD settings for different customers to make sure that proven-practices and guidelines are followed, including Conditional Access.

This has previously been done using mutliple tools, such as: PowerShell, Graph API, the portal itself, and other third party solutions relying on apps leveraging APIs to document and export data from the cloud.

Why is that an issue? Well for most orgs it isn’t, provided that you are protecting your admin accounts and admin access to Azure AD by leveraging PAWs, Cloud Only accounts using PIM et cetera. But, when we are called to action, we seldom have the pleasure of using a PAW since we are external consultants.

This lead to an idea. Would it be possible to export all the relevant data using the Azure AD PowerShell module? Sadly, no. Some of the data is still in the older MSOnline PS module, or you might need to use MS Graph (which requires an app registration and delegated permissions). Can we Export CA Policies via PowerShell?

Using PowerShell to export CA Policies

There are cmdlets for getting all your Conditional Access Policies but these are not readable… Or are they? Okay, they are redable from a PowerShell prompt by simply running:

Import-Module AzureADPreview
Connect-AzureAD
Get-AzureADMSConditionalAccessPolicy -PolicyId <PolicyId of the CA Policy you want to export>

More information on the cmdlet can be found here: https://learn.microsoft.com/en-us/powershell/module/azuread/get-azureadmsconditionalaccesspolicy?view=azureadps-2.0-preview

This does not give us any valuable data by default though, as presented in the image below:

Default output from the Get-AzureADMSConditionalAccessPolicy

We could drill into each property by ways of (Get-AzureADMSConditionalAccessPolicy).Condtions.Users/Applications/Platforms etc to get the data we´re looking for:

Drill into each object of the CA Policies

But it only returns the ObjectId property of each object, and I wanted the output to show a readable name of these objects. We can do this by looping through each property in different ways:

Example 1: foreach loop in PowerShell

Looping through the data in a foreach loop.

$Excludedusers = (Get-AzureADMSConditionalAccessPolicy -PolicyId f7406cc1-2ce6-4a68-995e-68e01731248b).Conditions.Users.ExcludeUsers
foreach ($excludeduser in $excludedusers){Get-AzureADUser -ObjectId $excludeduser}

Example 2: Piping out the data into string format, perform some data modification and look it up:

Lookup properties by modifying the data into separate strings

$String = $ExcludedUsers | Out-String
#Lookup 'Excluded Users' by performing data modification.
#Convert to Json
$ExcludedUsersToJson = $ExcludedUsers | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonExcludedUsers = $ExcludedUsersToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableExlucedUsers = $ReplaceDataInJsonExcludedUsers.Replace('"','')
#Trim the last "," to construct correct amount of strings
$TrimStringExcludedUsers = $ReplaceDataInJsonSplittableExlucedUsers.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringExcludedUsers = $TrimStringExcludedUsers -split ","
$ExcludedUsersLookup = Get-AzureADUser -All:$true | Where-Object {$_.ObjectId -In $SplitStringExcludedUsers}

By using the 2nd logic we are able to gather all interesting data about the CA Policies in a tenant and exporting it to a very readable JSON file. Which we can use as input for a report or just looking at how our policies are configured at any given time.

NOTE! If you have Linux configured in the Device Conditions, your call to the endpoint behind Get-AzureADMSConditionalAccessPolicy will fail and throw an error:

The error thrown if you have Linux in any device conditions

I´m told that Microsoft is aware of this error but will not rectify it since the AzureAD PowerShell Module is soon to be replaced with the Graph PowerShell modules instead. Which is a shame since the Graph module does not contain all relevant endpoints just yet..

The Script to export CA Policies

Since the Linux error kind of caught me off-guard, we need to get all the Policy Ids from somewhere else. Enter AADInternals: https://aadinternals.com/aadinternals/

AADInternals is developed by a fellow Security MVP Nestori Syynimää and is just the thing we need to get these Policy Ids.

We also need the MSOnline module for the role lookup part, since the AzureAD Module does not contain all the roles just like that.

Before running this script we need to run the indvidual cmdlets to get access tokens issued:

Connect-MsolService

Get-AADIntAccessTokenForAADGraph

And now, we are ready to roll!

Write-Host "Gathering Conditional Access Policies" -ForegroundColor Yellow
$ExportDateTime = Get-Date -Format "MM/dd/yyyy HH:mm:ss K"
$AADIntCAPolicies = Get-AADIntConditionalAccessPolicies | Where-Object displayName -NE "Default Policy" | Select-Object objectId, policyDetail
$CAPolicies = foreach ($AADIntCAPolicy in $AADIntCAPolicies){
Get-AzureADMSConditionalAccessPolicy -PolicyId $AADIntCAPolicy.objectid
}
$Policies = forEach ($CAPolicy in $CAPolicies){

$PolicyId = $CAPolicy.Id
$PolicyDisplayName = $CAPolicy.DisplayName
$PolicyState = $CAPolicy.State
$IncludedUsers = $CAPolicy.Conditions.Users.IncludeUsers | Out-String
$ExcludedUsers = $CAPolicy.Conditions.Users.ExcludeUsers | Out-String
$IncludedGroups = $CAPolicy.Conditions.Users.IncludeGroups | Out-String
$ExcludedGroups = $CAPolicy.Conditions.Users.ExcludeGroups | Out-String
$IncludedRoles = $CAPolicy.Conditions.Users.IncludeRoles | Out-String
$ExcludedRoles = $CAPolicy.Conditions.Users.ExcludeRoles | Out-String
$IncludedApplications = $CAPolicy.Conditions.Applications.IncludeApplications | Out-String $ExcludedApplications = $CAPolicy.Conditions.Applications.ExcludeApplications | Out-String $UserActions = $CAPolicy.Conditions.Applications.IncludeUserActions | Out-String $AuthenticationContexts = $CAPolicy.Conditions.Applications.IncludeAuthenticationContextClassReferences | Out-String $IncludedPlatforms = $CAPolicy.Conditions.Platforms.IncludePlatforms | Out-String $ExcludedPlatforms = $CAPolicy.Conditions.Platforms.ExcludePlatforms | Out-String $IncludedLocations = $CAPolicy.Conditions.Locations.IncludeLocations | Out-String $ExcludedLocations = $CAPolicy.Conditions.Locations.ExcludeLocations | Out-String $UserRiskLevels = $CAPolicy.Conditions.UserRiskLevels | Out-String
$SignInRiskLevels = $CAPolicy.Conditions.SignInRiskLevels | Out-String
$ClientAppTypes = $CAPolicy.Conditions.ClientAppTypes | Out-String
$IncludedDevices = $CAPolicy.Conditions.Devices.IncludeDevices | Out-String
$ExcludedDevices = $CAPolicy.Conditions.Devices.ExcludeDevices | Out-String
$DeviceFilterMode = $CAPolicy.Conditions.Devices.DeviceFilter.Mode | Out-String $DeviceFilterRule = $CAPolicy.Conditions.Devices.DeviceFilter.Rule | Out-String
$GrantControls = $CAPolicy.GrantControls.BuiltInControls | Out-String $CustomAuthentictationFactors = $CAPolicy.GrantControls.CustomAuthenticationFactors | Out-String $TermsOfUse = $CAPolicy.GrantControls.TermsOfUse | Out-String
$ApplicationEnforcedRestrictions = $CAPolicy.SessionControls.ApplicationEnforceRestrictions | Out-String
$MCAS = $CAPolicy.SessionControls.CloudAppSecurity | Out-String
$SignInFrequencyValue = $CAPolicy.SessionControls.SignInFrequency.Value | Out-String $SignInFrequencyType = $CAPolicy.SessionControls.SignInFrequency.Type | Out-String $PersistenBrowser = $CAPolicy.SessionControls.PersistentBrowser.Mode | Out-String ##########################################################################
# Lookup 'Included Users' # ##########################################################################
#Lookup 'Included Users' by performing data modification.
#Convert to Json
$IncludedUsersToJson = $IncludedUsers | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonIncludedUsers = $IncludedUsersToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableIncludedUsers = $ReplaceDataInJsonIncludedUsers.Replace('"','') #Trim the last "," to construct correct amount of strings
$TrimStringIncludedUsers = $ReplaceDataInJsonSplittableIncludedUsers.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringIncludedUsers = $TrimStringIncludedUsers -split ","
#Lookup AzureAD users by using the ObjectId value
$IncludedUsersLookup = Get-AzureADUser -All:$true | Where-Object {$_.ObjectId -In $SplitStringIncludedUsers} ##########################################################################
# Lookup 'Excluded Users' # ##########################################################################
#Lookup 'Excluded Users' by performing data modification.
#Convert to Json $ExcludedUsersToJson = $ExcludedUsers | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonExcludedUsers = $ExcludedUsersToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableExlucedUsers = $ReplaceDataInJsonExcludedUsers.Replace('"','')
#Trim the last "," to construct correct amount of strings
$TrimStringExcludedUsers = $ReplaceDataInJsonSplittableExlucedUsers.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringExcludedUsers = $TrimStringExcludedUsers -split ","
$ExcludedUsersLookup = Get-AzureADUser -All:$true | Where-Object {$_.ObjectId -In $SplitStringExcludedUsers} ##########################################################################
# Lookup 'Included Groups' # ##########################################################################
#Lookup 'Included Groups' by performing data modification.
#Convert to Json
$IncludedGroupsToJson = $IncludedGroups | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonIncludedGroups = $IncludedGroupsToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableIncludedGroups = $ReplaceDataInJsonIncludedGroups.Replace('"','') #Trim the last "," to construct correct amount of strings
$TrimStringIncludedGroups= $ReplaceDataInJsonSplittableIncludedGroups.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringIncludedGroups = $TrimStringIncludedGroups -split ","
$IncludedGroupsLookup = Get-AzureADGroup -All:$true | Where-Object {$_.ObjectId -In $SplitStringIncludedGroups} ##########################################################################
# Lookup 'Excluded Groups' # ##########################################################################
#Lookup 'Excluded Groups' by performing data modification.
#Convert to Json
$ExcludedGroupsToJson = $ExcludedGroups | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonExcludedGroups = $ExcludedGroupsToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableExcludedGroups = $ReplaceDataInJsonExcludedGroups.Replace('"','') #Trim the last "," to construct correct amount of strings
$TrimStringExcludedGroups= $ReplaceDataInJsonSplittableExcludedGroups.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringExcludedGroups = $TrimStringExcludedGroups -split ","
$ExcludedGroupsLookup = Get-AzureADGroup -All:$true | Where-Object {$_.ObjectId -In $SplitStringExcludedGroups} ##########################################################################
# Lookup 'Included Roles' # ##########################################################################
#Lookup 'Included Roles' by performing data modification.
#Convert to Json $IncludedRolesToJson = $IncludedRoles | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonIncludedRoles = $IncludedRolesToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableIncludedRoles = $ReplaceDataInJsonIncludedRoles.Replace('"','') #Trim the last "," to construct correct amount of strings
$TrimStringIncludedRoles= $ReplaceDataInJsonSplittableIncludedRoles.TrimEnd(',') #Split the string into multiple strings using "," as a separator
$SplitStringIncludedRoles = $TrimStringIncludedRoles -split ","
$IncludedRolesLookup = Get-MsolRole | Where-Object {$_.ObjectId -In $SplitStringIncludedRoles} ##########################################################################
# Lookup 'Excluded Roles' # ##########################################################################
#Lookup 'Excluded Roles' by performing data modification.
#Convert to Json
$ExcludedRolesToJson = $ExcludedRoles | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonExcludedRoles = $ExcludedRolesToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableExcludedRoles = $ReplaceDataInJsonExcludedRoles.Replace('"','') #Trim the last "," to construct correct amount of strings
$TrimStringExcludedRoles= $ReplaceDataInJsonSplittableExcludedRoles.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringExcludedRoles = $TrimStringExcludedRoles -split "," $ExcludedRolesLookup = Get-MsolRole | Where-Object {$_.ObjectId -In $SplitStringExcludedRoles} ##########################################################################
# Lookup 'Included Applications' # ##########################################################################
#Lookup 'Included Applications' by performing data modification.
#Convert to Json
$AllApplicationServicePrincipals = (Get-AzureADServicePrincipal -All:$true).AppId $IncludedAppsToJson = $IncludedApplications | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonIncludedApps = $IncludedAppsToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableIncludedApps = $ReplaceDataInJsonIncludedApps.Replace('"','')
#Trim the last "," to construct correct amount of strings
$TrimStringIncludedApps= $ReplaceDataInJsonSplittableIncludedApps.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringIncludedApps = $TrimStringIncludedApps -split ","
$IncludedAppsLookup = Get-AzureADServicePrincipal -All:$true | Where-Object {$_.AppId -In $SplitStringIncludedApps} ##########################################################################
# Lookup 'Excluded Applications' # ##########################################################################
#Lookup 'Excluded Applications' by performing data modification.
#Convert to Json $ExcludedAppsToJson = $ExcludedApplications | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonExcludedApps = $ExcludedAppsToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable
$ReplaceDataInJsonSplittableExcludedApps = $ReplaceDataInJsonExcludedApps.Replace('"','')
#Trim the last "," to construct correct amount of strings
$TrimStringExcludedApps= $ReplaceDataInJsonSplittableExcludedApps.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringExcludedApps = $TrimStringExcludedApps -split ","
$ExcludedAppsLookup = Get-AzureADServicePrincipal -All:$true | Where-Object {$_.AppId -In $SplitStringExcludedApps} ##########################################################################
# Lookup 'Included Locations' # ########################################################################## $IncludedLocationsToJson = $IncludedLocations | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonIncludedLocations = $IncludedLocationsToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable $ReplaceDataInJsonSplittableIncludedLocations = $ReplaceDataInJsonIncludedLocations.Replace('"','')
#Trim the last "," to construct correct amount of strings
$TrimStringIncludedLocations = $ReplaceDataInJsonSplittableIncludedLocations.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringIncludedLocations = $TrimStringIncludedLocations -split ","
$IncludedLocationsName = Get-AzureADMSNamedLocationPolicy | Where-Object {$_.Id -in $SplitStringIncludedLocations} ##########################################################################
# Lookup 'Excluded Locations' # ########################################################################## $ExcludedLocationsToJson = $ExcludedLocations | ConvertTo-Json
#Replace \r\n at the end of each object in the string with ,
$ReplaceDataInJsonExcludedLocations = $ExcludedLocationsToJson.Replace('\r\n',',')
#Replace " with nothing to make the string splittable $ReplaceDataInJsonSplittableExcludedLocations = $ReplaceDataInJsonExcludedLocations.Replace('"','')
#Trim the last "," to construct correct amount of strings
$TrimStringExcludedLocations = $ReplaceDataInJsonSplittableExcludedLocations.TrimEnd(',')
#Split the string into multiple strings using "," as a separator
$SplitStringExcludedLocations = $TrimStringExcludedLocations -split ","
$ExcludedLocationsName = Get-AzureADMSNamedLocationPolicy | Where-Object {$_.Id -in $SplitStringExcludedLocations}

$CACustomObject = New-Object -TypeName PSObject
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Policy ObjectId" -Value $PolicyId
$CACustomObject | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value $PolicyDisplayName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "State" -Value $PolicyState $CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Users" -Value $IncludedUsers
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Users (Lookup)" -Value $IncludedUsersLookup.UserPrincipalName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Users" -Value $ExcludedUsers
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Users (Lookup)" -Value $ExcludedUsersLookup.UserPrincipalName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Groups" -Value $IncludedGroups
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Groups (Lookup)" -Value $IncludedGroupsLookup.DisplayName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Groups" -Value $ExcludedGroups
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Groups (Lookup)" -Value $ExcludedGroupsLookup.DisplayName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Roles" -Value $IncludedRoles
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Roles (Lookup)" -Value $IncludedRolesLookup.Name
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Roles" -Value $ExcludedRoles
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Roles (Lookup)" -Value $ExcludedRolesLookup.Name
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Applications" -Value $IncludedApplications
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Applications (Lookup)" -Value $IncludedAppsLookup.DisplayName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Applications" -Value $ExcludedApplications
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Applications (Lookup)" -Value $ExcludedAppsLookup.DisplayName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "User Actions" -Value $UserActions $CACustomObject | Add-Member -MemberType NoteProperty -Name "Authentication Contexts" -Value $AuthenticationContexts
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Device Platforms" -Value $IncludedPlatforms
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Device Platforms" -Value $ExcludedPlatforms
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Locations" -Value $IncludedLocations
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Locations (Lookup)" -Value $IncludedLocationsName.DisplayName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Locations" -Value $ExcludedLocations
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Excluded Locations (Lookup)" -Value $ExcludedLocationsName.DisplayName
$CACustomObject | Add-Member -MemberType NoteProperty -Name "User Risk Levels" -Value $UserRiskLevels
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Sign Risk Levels" -Value $SignInRiskLevels
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Client App Types" -Value $ClientAppTypes
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Included Devices" -Value $IncludedDevices
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Exclude Devices" -Value $ExcludedDevices
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Device Filter Mode" -Value $DeviceFilterMode
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Device Filter Rule" -Value $DeviceFilterRule
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Grant Controls" -Value $GrantControls
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Grant Controls Custom Authentication Factors" -Value $CustomAuthentictationFactors
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Grant Controls Terms Of Use" -Value $TermsOfUse
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Application Enforced Restrictions" -Value $ApplicationEnforcedRestrictions
$CACustomObject | Add-Member -MemberType NoteProperty -Name "MCAS" -Value $MCAS
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Sign In Frequency Value" -Value $SignInFrequencyValue
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Sign In Frequency Type" -Value $SignInFrequencyType
$CACustomObject | Add-Member -MemberType NoteProperty -Name "Persistent Browser Mode" -Value $PersistenBrowser $CACustomObject
}

$CAResult = $Policies | Sort-Object DisplayName -Descending
$CAExportResult = $CAResult | ConvertTo-Json
$CAExportableResult = $CAExportResult.Replace('\r\n',',')
$CADateTimeExport = $ExportDateTime | Out-File -FilePath $OutputPath\TSxCAPolicies.json -Encoding ascii
$CAExportableResult | Out-File -FilePath $OutputPath\TSxCAPolicies.json -Encoding ascii -Append

$CAMisconfig = $CAResult | Out-GridView -PassThru -Title "Select any misconfigured CA Policy and click 'OK' to export it. If none, select 'Cancel'."
$CAMisconfigExport = $CAMisconfig | ConvertTo-Json
$CAMisconfigExportable = $CAMisconfigExport.Replace('\r\n',',')
$CADateTimeMisconfig = $ExportDateTime | Out-File -FilePath $OutputPath\TSxMisconfigCA.json -Encoding ascii
$CAMisconfigExportable | Out-File -FilePath $OutputPath\TSxMisconfigCA.json -Encoding ascii -Append

And there we go! We even get a nice Out-GridView of all CA Policies and can select the ones that might be misconfigured for export:

Out-GridView of all CA Policies gathered by the script
Example JSON report of the CA Policies

The script is also uploaded to my Public Github Repo: hedbergtech/AzureActiveDirectory and it contains even more fun, exportable data using the three modules in this blog post.

Summary

This endeavour took me quite some time to figure out, but witht his approach (even though the script requires multiple sign-ins) we are able to successfully export relevant data without exposing or adding any app registrations to Azure AD. The script is subject to frequent changes.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.