├── ConditionalAccess ├── ConditionalAccess.psd1 ├── ConditionalAccess.psm1 ├── Private │ ├── ConvertFrom-AgreementDisplayNametoGuid.ps1 │ ├── ConvertFrom-AgreementGuidToDisplayName.ps1 │ ├── ConvertFrom-ApplicationDisplayNameToGUID.ps1 │ ├── ConvertFrom-ApplicationGUIDtoDisplayName.ps1 │ ├── ConvertFrom-GroupDisplayNameToGUID.ps1 │ ├── ConvertFrom-GroupGUIDToDisplayName .ps1 │ ├── ConvertFrom-LocationDisplayNametoGUID.ps1 │ ├── ConvertFrom-LocationGUIDtoDisplayName.ps1 │ ├── ConvertFrom-RoleDisplayNametoGUID.ps1 │ ├── ConvertFrom-RoleGUIDtoDisplayName.ps1 │ ├── ConvertFrom-UserDisplayNameToGUID.ps1 │ └── ConvertFrom-UserGUIDToDisplayName.ps1 └── Public │ ├── Deploy-ConditionalAccessPolicies.ps1 │ ├── Get-AccessToken.ps1 │ ├── Get-ConditionalAccessPolicy.ps1 │ ├── Get-ConditionalAccessPolicyFile.ps1 │ ├── New-ConditionalAccessPolicy.ps1 │ ├── Remove-ConditionalAccessPolicy.ps1 │ └── Set-ConditionalAccessPolicy.ps1 ├── Examples ├── Policy │ ├── CA-01- All Apps - All Admins - Require MFA.json │ ├── CA-02- All Apps - All Users - Require MFA or Trusted Device.json │ ├── Convert.Json │ └── Temp │ │ ├── CA-01- All Apps - All Admins - Require MFA.json │ │ └── CA-02- All Apps - All Admins - Require MFA.json └── Script │ └── Demo.ps1 ├── LICENSE └── README.md /ConditionalAccess/ConditionalAccess.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'ConditionalAccess' 3 | # 4 | # Generated by: Maatschap Fortigi 5 | # 6 | # Generated on: 16/06/2020 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'ConditionalAccess.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.0.2020.09.09' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'd11aa11b-ee4f-43ce-8c07-5d2ce4fa4533' 22 | 23 | # Author of this module 24 | Author = 'William Overweg & Wim van den Heijkant' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Fortigi' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2020 Fortigi.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'This module allows you to manage Conditional Access Policy from PowerShell. Is uses the Microsoft Grap API. For more info https://github.com/Fortigi/ConditionalAccess' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | RequiredModules = @( 55 | ) 56 | 57 | # Assemblies that must be loaded prior to importing this module 58 | # RequiredAssemblies = @() 59 | 60 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 61 | # ScriptsToProcess = @() 62 | 63 | # Type files (.ps1xml) to be loaded when importing this module 64 | # TypesToProcess = @() 65 | 66 | # Format files (.ps1xml) to be loaded when importing this module 67 | # FormatsToProcess = @() 68 | 69 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 70 | # NestedModules = @() 71 | 72 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 73 | FunctionsToExport = @('Get-AccessToken', 'New-ConditionalAccessPolicy', 'Remove-ConditionalAccessPolicy', 'Get-ConditionalAccessPolicy','Get-ConditionalAccessPolicyFile','Set-ConditionalAccessPolicy') 74 | 75 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 76 | CmdletsToExport = @() 77 | 78 | # Variables to export from this module 79 | VariablesToExport = '*' 80 | 81 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 82 | AliasesToExport = @() 83 | 84 | # DSC resources to export from this module 85 | # DscResourcesToExport = @() 86 | 87 | # List of all modules packaged with this module 88 | # ModuleList = @() 89 | 90 | # List of all files packaged with this module 91 | # FileList = @() 92 | 93 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 94 | PrivateData = @{ 95 | 96 | PSData = @{ 97 | 98 | # Tags applied to this module. These help with module discovery in online galleries. 99 | # Tags = @() 100 | 101 | # A URL to the license for this module. 102 | # LicenseUri = '' 103 | 104 | # A URL to the main website for this project. 105 | # ProjectUri = '' 106 | 107 | # A URL to an icon representing this module. 108 | # IconUri = '' 109 | 110 | # ReleaseNotes of this module 111 | # ReleaseNotes = '' 112 | 113 | } # End of PSData hashtable 114 | 115 | } # End of PrivateData hashtable 116 | 117 | # HelpInfo URI of this module 118 | # HelpInfoURI = '' 119 | 120 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 121 | # DefaultCommandPrefix = '' 122 | 123 | } 124 | 125 | -------------------------------------------------------------------------------- /ConditionalAccess/ConditionalAccess.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | MIT License 3 | 4 | Copyright (c) 2019 Fortigi. All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | #> 24 | 25 | 26 | 27 | $Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue) 28 | $Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue) 29 | 30 | #Dot source the files 31 | Foreach ($import in @($Public + $Private)) { 32 | Try { 33 | . $import.fullname 34 | } 35 | Catch { 36 | Write-Error -Message "Failed to import function $($import.fullname): $_" 37 | } 38 | } 39 | 40 | Export-ModuleMember -Function $Public.Basename -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-AgreementDisplayNametoGuid.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-AgreementDisplayNameToGUID { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-AgreementDisplayNameToGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of Agreements to their respective GUIDs as they exist in the targeted AzureAD tenant. 5 | 6 | .Description 7 | The command takes the array of displaynames of applications from the JSON file and checks their existence in the targeted AzureAD tenant. 8 | 9 | Prerequisites 10 | - Valid Access Token with the minimum following API permissions: 11 | Agreement.Read.All 12 | 13 | -Optional permission for automatic Agreement creation through use of the -CreateMissingAgreements parameter 14 | Agreement.Create OR Agreement.Readwrite.All 15 | 16 | .Example 17 | [array]$AgreementDisplayNames = "InclusionAgreement1" 18 | ConvertFrom-AgreementDisplayNameToGUID -AgreementDisplayNames $AgreementDisplayNames -Force $true -AccessToken $AccessToken 19 | #> 20 | Param 21 | ( 22 | [Parameter(Mandatory = $false)] 23 | [array]$AgreementDisplayNames, 24 | [Parameter(Mandatory = $true)] 25 | $AccessToken, 26 | [parameter(mandatory = $true)] 27 | $PathConvertFile, 28 | [parameter(mandatory = $true)] 29 | $TargetTenantName 30 | ) 31 | 32 | [array]$AgreementGuids = $null 33 | 34 | if ($AgreementDisplayNames) { 35 | $ConvertFile = Get-Content -Path $PathConvertFile -Raw | ConvertFrom-Json 36 | } 37 | Foreach ($AgreementDisplayname in $AgreementDisplaynames) { 38 | If (!$ConvertFile) { 39 | Throw "Please give the correct path for the Convert.Json file in order to convert TermsOfUse DisplayName to their corresponding GUIDs" 40 | } 41 | $Guid = $null 42 | $Guid = $ConvertFile.termsofuse.tenant | where-object { $_.tenantname -eq $TargetTenantName } 43 | If ($guid) { 44 | $AgreementGuids += $guid.TermsOfUseObjectID 45 | } 46 | if (!$Guid) { 47 | Throw "Mismatch between TargetTenantName input and TenantName in JsonFile" 48 | } 49 | } 50 | Return $AgreementGuids 51 | } 52 | 53 | 54 | 55 | 56 | #Below in preparation for when Agreements are supported by the Graph API via application permissions 57 | # Foreach ($AgreementDisplayname In $AgreementDisplaynames) { 58 | # $URI = "https://graph.microsoft.com/beta/Agreements/$AgreementDisplayname" 59 | # $AgreementObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 60 | # if ($AgreementObject.count -gt 1) { 61 | # Write-Warning "More than one Object was found for Agreement DisplayName: $LocationDisplayName" 62 | # } 63 | # #Add ID to AgreementDisplaynames 64 | # $DisplayName = ($AgreementObject.displayName) 65 | # [array]$AgreementDisplayNames += $DisplayName 66 | # } 67 | # Return [array]$AgreementDisplayNames 68 | #} 69 | 70 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-AgreementGuidToDisplayName.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-AgreementGuidToDisplayName { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-AgreementDisplayNameToGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of Agreements to their respective GUIDs as they exist in the targeted AzureAD tenant. 5 | 6 | .Description 7 | The command takes the array of displaynames of applications from the JSON file and checks their existence in the targeted AzureAD tenant. 8 | 9 | Prerequisites 10 | - Valid Access Token with the minimum following API permissions: 11 | Agreement.Read.All 12 | 13 | -Optional permission for automatic Agreement creation through use of the -CreateMissingAgreements parameter 14 | Agreement.Create OR Agreement.Readwrite.All 15 | 16 | .Example 17 | [array]$AgreementDisplayNames = "InclusionAgreement1" 18 | ConvertFrom-AgreementDisplayNameToGUID -AgreementDisplayNames $AgreementDisplayNames -Force $true -AccessToken $AccessToken 19 | #> 20 | Param 21 | ( 22 | [Parameter(Mandatory = $false)] 23 | [array]$AgreementGuids, 24 | [Parameter(Mandatory = $true)] 25 | $AccessToken, 26 | [parameter(mandatory = $false)] 27 | $PathConvertFile 28 | ) 29 | 30 | [array]$AgreementDisplayNames = $null 31 | 32 | Foreach ($AgreementGuid in $AgreementGuids) { 33 | $ConvertFile = Get-Content -Path $PathConvertFile -Raw | ConvertFrom-Json 34 | If (!$ConvertFile) { 35 | Throw "One or more policies contain TermsofUse objects. Please give the correct path for the Convert.Json file in order to convert TermsOfUse GUIDs to their corresponding DisplayNames" 36 | } 37 | $Tenant = $null 38 | $Tenant = $ConvertFile | where-object { $_.TermsOfUse.tenant.TermsOfUseObjectID -eq $AgreementGuid } 39 | If ($Tenant) { 40 | Write-Host "Converting Guid: $agreementGuid to corresponding DiplayName." 41 | $AgreementDisplayNames += ($convertfile.Termsofuse.Displayname) 42 | } 43 | if (!$Tenant) { 44 | Throw "Inconsistency between GUIDs in Graph: $AgreementGuid and GUIDs in Convert.Json. Please update the file to match" 45 | } 46 | } 47 | Return $AgreementDisplayNames 48 | } 49 | 50 | 51 | 52 | 53 | #Below in preparation for when Agreements are supported by the Graph API via application permissions 54 | # Foreach ($AgreementGuid In $AgreementGuids) { 55 | # $URI = "https://graph.microsoft.com/beta/Agreements/$AgreementGuid" 56 | # $AgreementObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 57 | # if ($AgreementObject.count -gt 1) { 58 | # Write-Warning "More than one Object was found for Agreement DisplayName: $LocationDisplayName" 59 | # } 60 | # #Add ID to AgreementGuids 61 | # $DisplayName = ($AgreementObject.displayName) 62 | # [array]$AgreementDisplayNames += $DisplayName 63 | # } 64 | # Return [array]$AgreementDisplayNames 65 | #} 66 | 67 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-ApplicationDisplayNameToGUID.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-ApplicationDisplayNameToGUID { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-ApplicationDisplayNameToGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of applications to their respective GUIDs as they exist in the targeted AzureAD tenant. 5 | 6 | .Description 7 | The command takes the array of displaynames of applications from the JSON file and checks their existence in the targeted AzureAD tenant. 8 | 9 | Prerequisites 10 | - Valid Access Token with the minimum following API permissions: 11 | Application.Read.All 12 | 13 | .Example 14 | [array]$YourApplicatioName = "ApplicationY" 15 | ConvertFrom-ApplicationDisplayNameToGUID -ApplicationDisplayNames $ApplicationY -AccessToken $AccessToken 16 | #> 17 | Param 18 | ( 19 | [Parameter(Mandatory = $false)] 20 | [array]$ApplicationDisplayNames, 21 | [Parameter(Mandatory = $true)] 22 | $AccessToken 23 | ) 24 | 25 | [array]$ApplicationGuids = $null 26 | 27 | #$ApplicationDisplayNames = "Microsoft Azure Management" 28 | 29 | Foreach ($ApplicationDisplayName In $ApplicationDisplayNames) { 30 | $DontSearchGraph = $null 31 | 32 | Switch ($ApplicationDisplayName.ToString().ToLower()){ 33 | #"Office365" is the represetation of the "Office 365 (preview)" Object in the GUI for Conditional Access through the Azure Portal. Static Converted is required as no object exists in the directory. 34 | "office 365" { 35 | $DontSearchGraph += 1 36 | $ApplicationGuids += "Office365"; Break 37 | 38 | } 39 | #"All" is the represetation all Apps both in and outside of the directory. Static Converted is required as no (single) object exists in the directory. 40 | "all" { 41 | $DontSearchGraph += 1 42 | $ApplicationGuids = $null 43 | $ApplicationGuids += "All"; Break 44 | } 45 | #The GUID for "Microsoft Azure Management" as found in the GUI for Conditional Access through the Azure Portal does not exist in the Directory, as it contains several applications and portals in one. Static Converted is necessary. 46 | "microsoft azure management" { 47 | $DontSearchGraph += 1 48 | $ApplicationGuids += "797f4846-ba00-4fd7-ba43-dac1f8f63013"; Break 49 | } 50 | } 51 | #If none of the staticly Converted GUIDs are triggered, search in Graph. 52 | If (!$DontSearchGraph){ 53 | $URI = "https://graph.microsoft.com/beta/ServicePrincipals?" + '$filter' + "=displayName eq '$ApplicationDisplayName'" 54 | $ApplicationObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 55 | Start-Sleep -Seconds 1 56 | 57 | If (!$ApplicationObject.value) { 58 | Throw "Application: $ApplicationDisplayName specified in policy was not found." 59 | } 60 | $ApplicationGuids += ($ApplicationObject.value.appId) 61 | } 62 | } 63 | Return $ApplicationGuids 64 | } 65 | 66 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-ApplicationGUIDtoDisplayName.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-ApplicationGUIDToDIsplayName { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-ApplicationGUIDToDIsplayName command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of applications to their respective GUIDs as they exist in the targeted AzureAD tenant. 5 | 6 | .Description 7 | The command takes the array of displaynames of applications from the JSON file and checks their existence in the targeted AzureAD tenant. 8 | 9 | Prerequisites 10 | - Valid Access Token with the minimum following API permissions: 11 | Application.Read.All 12 | 13 | .Example 14 | [array]$YourApplicatioName = "ApplicationY" 15 | ConvertFrom-ApplicationGUIDToDIsplayName -ApplicationDisplayNames $ApplicationY -AccessToken $AccessToken 16 | #> 17 | Param 18 | ( 19 | [Parameter(Mandatory = $false)] 20 | [array]$ApplicationGUIDs, 21 | [Parameter(Mandatory = $true)] 22 | $AccessToken 23 | ) 24 | 25 | [array]$ApplicationDisplayNames = $null 26 | 27 | #$ApplicationDisplayNames = "Microsoft Azure Management" 28 | 29 | Foreach ($ApplicationGuid In $ApplicationGuids) { 30 | $DontSearchGraph = $null 31 | 32 | Switch ($ApplicationGuid.ToString().ToLower()){ 33 | #"Office365" is the represetation of the "Office 365 (preview)" Object in the GUI for Conditional Access through the Azure Portal. Static Converted is required as no object exists in the directory. 34 | "office365" { 35 | $DontSearchGraph += 1 36 | $ApplicationDisplayNames += "Office 365"; Break 37 | 38 | } 39 | #"All" is the represetation all Apps both in and outside of the directory. Static Converted is required as no (single) object exists in the directory. 40 | "all" { 41 | $DontSearchGraph += 1 42 | $ApplicationGuids = $null 43 | $ApplicationDisplayNames += "All"; Break 44 | } 45 | #The GUID for "Microsoft Azure Management" as found in the GUI for Conditional Access through the Azure Portal does not exist in the Directory, as it contains several applications and portals in one. Static Converted is necessary. 46 | "797f4846-ba00-4fd7-ba43-dac1f8f63013" { 47 | $DontSearchGraph += 1 48 | $ApplicationDisplayNames += "Microsoft Azure Management"; Break 49 | } 50 | } 51 | #If none of the staticly Converted GUIDs are triggered, search in Graph. 52 | If (!$DontSearchGraph){ 53 | $URI = "https://graph.microsoft.com/beta/ServicePrincipals?" + '$filter' + "=appId eq '$ApplicationGuid'" 54 | $ApplicationObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 55 | Start-Sleep -Seconds 1 56 | 57 | If (!$ApplicationObject.value) { 58 | Throw "Application: $ApplicationGuid specified in policy was not found." 59 | } 60 | $ApplicationDisplayNames += ($ApplicationObject.value.displayName) 61 | } 62 | } 63 | Return $ApplicationDisplayNames 64 | } 65 | 66 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-GroupDisplayNameToGUID.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-GroupDisplayNameToGUID { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-GroupDisplayNameToGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of Groups to their respective GUIDs as they exist in the targeted AzureAD tenant. 5 | 6 | .Description 7 | The command takes the array of displaynames of applications from the JSON file and checks their existence in the targeted AzureAD tenant. 8 | 9 | Prerequisites 10 | - Valid Access Token with the minimum following API permissions: 11 | Group.Read.All 12 | 13 | -Optional permission for automatic group creation through use of the -CreateMissingGroups parameter 14 | Group.Create OR Group.Readwrite.All 15 | 16 | .Example 17 | [array]$GroupDisplayNames = "InclusionGroup1" 18 | ConvertFrom-GroupDisplayNameToGUID -GroupDisplayNames $GroupDisplayNames -Force $true -AccessToken $AccessToken 19 | #> 20 | Param 21 | ( 22 | [Parameter(Mandatory = $false)] 23 | [array]$GroupDisplayNames, 24 | [Parameter(Mandatory = $true)] 25 | $AccessToken, 26 | [Parameter(Mandatory = $False)] 27 | [System.Boolean]$CreateMissingGroups 28 | ) 29 | 30 | [array]$GroupGuids = $null 31 | 32 | Foreach ($GroupDisplayName In $GroupDisplayNames) { 33 | $URI = "https://graph.microsoft.com/beta/groups?" + '$filter' + "=displayName eq '$GroupDisplayName'" 34 | $GroupObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 35 | Start-Sleep -Seconds 1 36 | 37 | If (!$GroupObject.value) { 38 | If ($CreateMissingGroups -ne $true ) { 39 | Throw "The group specified in the policy JSON could not be found in AzureAD. Group Displayname: $GroupDisplayName. Use -Force paramater to auto create groups." 40 | } 41 | If ($CreateMissingGroups -eq $true ) { 42 | Write-host "Creating Azure AD Group: $GroupDisplayName" -ForegroundColor Yellow 43 | 44 | #Define group JSON template 45 | $GroupFile = '{ 46 | "description": "GrpDescription", 47 | "displayName": "GrpDisplayName", 48 | "mailEnabled": false, 49 | "mailNickname": "NotSet", 50 | "securityEnabled": true 51 | }' 52 | 53 | #Create a mailnickname 54 | $MailNickName = $GroupDisplayName.Replace(" ","") 55 | If ($MailNickName.Length -gt 19) { 56 | $MailNickName = $MailNickName.Substring(0,19) 57 | } 58 | 59 | #Convert GroupJSON to Powershell 60 | $GroupPS = $GroupFile | ConvertFrom-Json 61 | #Fill PS object with correct Displayname and Description 62 | $GroupPS.displayName = $GroupDisplayName 63 | $GroupPS.description = $GroupDisplayName 64 | $GroupPS.mailNickname = $MailNickName 65 | $GroupPS.mailEnabled = $False 66 | $GroupPS.securityEnabled = $true 67 | #Convert to JSON 68 | $NewGroupJson = $GroupPS | ConvertTo-Json 69 | #Create the group using the Json as input 70 | $URI = "https://graph.microsoft.com/beta/groups?" + '$filter' + "=displayName eq '$GroupDisplayName'" 71 | Invoke-RestMethod -Method Post -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } -Body $NewGroupJson -ContentType "application/json" | Out-Null 72 | #Delay after creation 73 | Start-Sleep -s 5 74 | #Fill GroupObject with the newly created group 75 | $URI = "https://graph.microsoft.com/beta/groups?" + '$filter' + "=displayName eq '$GroupDisplayName'" 76 | $GroupObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 77 | Start-Sleep -Seconds 1 78 | 79 | If ($GroupObject) { 80 | Write-host "Success" -ForegroundColor Green 81 | } 82 | Else { 83 | Throw "Error creating group." 84 | } 85 | } 86 | } 87 | #Add ID to GroupGuids 88 | $Guid = ($GroupObject.value.id) 89 | [array]$GroupGuids += $Guid 90 | } 91 | Return [array]$GroupGuids 92 | } -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-GroupGUIDToDisplayName .ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-GroupGuidToDisplayName { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-GroupDisplayNameToGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of Groups to their respective GUIDs as they exist in the targeted AzureAD tenant. 5 | 6 | .Description 7 | The command takes the array of displaynames of applications from the JSON file and checks their existence in the targeted AzureAD tenant. 8 | 9 | Prerequisites 10 | - Valid Access Token with the minimum following API permissions: 11 | Group.Read.All 12 | 13 | -Optional permission for automatic group creation through use of the -CreateMissingGroups parameter 14 | Group.Create OR Group.Readwrite.All 15 | 16 | .Example 17 | [array]$GroupDisplayNames = "InclusionGroup1" 18 | ConvertFrom-GroupDisplayNameToGUID -GroupDisplayNames $GroupDisplayNames -Force $true -AccessToken $AccessToken 19 | #> 20 | Param 21 | ( 22 | [Parameter(Mandatory = $false)] 23 | [array]$GroupGuids, 24 | [Parameter(Mandatory = $true)] 25 | $AccessToken 26 | ) 27 | 28 | [array]$GroupDisplayNames = $null 29 | 30 | Foreach ($GroupGuid In $GroupGuids) { 31 | $URI = "https://graph.microsoft.com/beta/groups/$GroupGuid" 32 | $GroupObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 33 | Start-Sleep -Seconds 1 34 | 35 | if ($GroupObject.count -gt 1) { 36 | Write-Warning "More than one Object was found for DisplayName: $LocationDisplayName" 37 | } 38 | #Add ID to GroupGuids 39 | $DisplayName = ($GroupObject.displayName) 40 | [array]$GroupDisplayNames += $DisplayName 41 | } 42 | Return [array]$GroupDisplayNames 43 | } 44 | 45 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-LocationDisplayNametoGUID.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-LocationDisplayNameToGUID { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-LocationDisplayNameToGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of Locations to their GUIDs as they exist in the targeted Tenant. 5 | 6 | .Description 7 | 8 | .Description 9 | The command takes the array of DisplayNames of Locations from the input in the parameter and checks their existence in the targeted AzureAD tenant. If the LocationDisplayName Exists the GUID is returned 10 | And added to the LocationGUIDs array. 11 | 12 | Prerequisites 13 | - Valid Access Token with the minimum following API permissions: 14 | Locations.Read.All 15 | 16 | 17 | .Example 18 | [array]$LocationDisplayNames = "william@fortigi.nl" 19 | ConvertFrom-LocationDisplayNameGUID -LocationDisplayNames $LocationDisplayNames -AccessToken $AccessToken 20 | #> 21 | 22 | Param 23 | ( 24 | [Parameter(Mandatory = $false)] 25 | [array]$LocationDisplayNames, 26 | [Parameter(Mandatory = $true)] 27 | $AccessToken 28 | ) 29 | 30 | [array]$LocationGuids = $null 31 | 32 | Foreach ($LocationDisplayName in $LocationDisplayNames) { 33 | 34 | If ($LocationDisplayName.ToString().ToLower() -ne "all") { 35 | $URI = "https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations?" + '$filter' + "=DisplayName eq '$LocationDisplayName'" 36 | $LocationObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 37 | Start-Sleep -Seconds 1 38 | If (!$LocationObject.value) { 39 | Throw "Location: $LocationDisplayName specified in the Policy was not found in the directory. Create Location, or update your policy." 40 | } 41 | if ($locationobject.value.count -gt 1){ 42 | Throw "More than one Object was found for Location DisplayName: $LocationDisplayName" 43 | } 44 | $LocationGuids += ($LocationObject.value.id) 45 | } 46 | Else { 47 | $LocationGuids = $null 48 | $LocationGuids += "All" 49 | } 50 | } 51 | Return $LocationGuids 52 | } 53 | 54 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-LocationGUIDtoDisplayName.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-LocationGUIDToDisplayName { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-LocationGUIDToLocationDisplayName command uses a Token from the "Get-AccessToken" command to convert the [array]LocationGuids of Locations to their DisplayNames as they exist in the targeted Tenant. 5 | 6 | .Description 7 | 8 | .Description 9 | The command takes the array of DisplayNames of Locations from the input in the parameter and checks their existence in the targeted AzureAD tenant. If the LocationDisplayName Exists the GUID is returned 10 | And added to the LocationGUIDs array. 11 | 12 | Prerequisites 13 | - Valid Access Token with the minimum following API permissions: 14 | Locations.Read.All 15 | 16 | 17 | .Example 18 | [array]$LocationGuids = "william@fortigi.nl" 19 | ConvertFrom-LocationGUIDToDisplayName -LocationGuids $LocationGuids -AccessToken $AccessToken 20 | #> 21 | 22 | Param 23 | ( 24 | [Parameter(Mandatory = $false)] 25 | [array]$LocationGuids, 26 | [Parameter(Mandatory = $true)] 27 | $AccessToken 28 | ) 29 | 30 | [array]$LocationDisplayNames = $null 31 | 32 | Foreach ($Locationguid in $LocationGuids) { 33 | 34 | $Locationguid = $Locationguid.ToString().ToLower() 35 | 36 | switch ($Locationguid) { 37 | 38 | #All locations 39 | "all" { 40 | $LocationDisplayNames = $null 41 | $LocationDisplayNames += "All" 42 | } 43 | 44 | #All trusted locations 45 | "alltrusted" { 46 | $LocationDisplayNames = $null 47 | $LocationDisplayNames += "AllTrusted" 48 | } 49 | 50 | #This option is not supported by Graph 51 | "00000000-0000-0000-0000-000000000000" { 52 | Write-Warning -Message "The MFA Trusted IPs selection is not yet supported via graph, please update policy to select the corresponding locations individually" 53 | } 54 | 55 | #All other locations. 56 | default { 57 | $URI = "https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations/$Locationguid" 58 | $LocationObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 59 | Start-Sleep -Seconds 1 60 | If (!$LocationObject) { 61 | Throw "Location: $Locationguid specified in the Policy was not found in the directory. Create Location, or update your policy." 62 | } 63 | $LocationDisplayNames += $LocationObject.DisplayName 64 | } 65 | } 66 | } 67 | 68 | Return $LocationDisplayNames 69 | } 70 | 71 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-RoleDisplayNametoGUID.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-RoleDisplayNametoGUID { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-RoleDisplayNametoGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of Roles to their GUIDs of the Roletemplates as they exist in the targeted Tenant. 5 | 6 | .Description 7 | The command fills an object with the Role Template objects as found in the target directory. It then cycles through each Role displayName and attempts to match The displayName from parameter to the DisplayName 8 | of the Role Template Object. If there is a match, the GUID is added for the conversion. If there is no match the command looks at directory roles to look for a match based on displayName. 9 | If there is a match, the GUID is added for the conversion. 10 | 11 | Prerequisites 12 | - Valid Access Token with the minimum following API permissions: 13 | RoleManagement.Read.Directory 14 | 15 | .Example 16 | [array]$RoleDisplayNames = "Company Administrator" 17 | ConvertFrom-GroupDisplayNameToGUID -RoleDisplayNames $RoleDisplayNames -Force $true -AccessToken $AccessToken 18 | #> 19 | Param 20 | ( 21 | [Parameter(Mandatory = $false)] 22 | [array]$RoleDisplayNames, 23 | [Parameter(Mandatory = $true)] 24 | $AccessToken 25 | ) 26 | #Empty role GUID array 27 | [array]$RoleGuids = $null 28 | 29 | #Get RoleTemplate Objects from Graph 30 | $URI = "https://graph.microsoft.com/beta/directoryRoletemplates" 31 | $RoleTemplates = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 32 | Start-Sleep -Seconds 1 33 | 34 | [array]$Roles = $Roletemplates.value 35 | 36 | If ($RoleDisplayNames -eq "All"){ 37 | [array]$RoleDisplayNames = $null 38 | Foreach ($Role In $Roles){ 39 | $RoleDisplayNames += $Role.displayName 40 | } 41 | } 42 | 43 | #For each in Policy File stated Role (DisplayName), attempt to map ObjectIDs based on Matching DisplayNames. 44 | Foreach ($RoleDisplayName in $RoleDisplayNames) { 45 | 46 | #Find role in Default roles set. 47 | $Found = $Roles | Where-Object { $_.DisplayName -eq $RoleDisplayName } 48 | 49 | If ($Found) { 50 | $RoleGuids += ($Found.Id) 51 | } 52 | Else { 53 | $URI = "https://graph.microsoft.com/beta/directoryRoles?" + '$filter' + "=displayName eq '$RoleDisplayName'" 54 | $RoleObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 55 | Start-Sleep -Seconds 1 56 | 57 | If (!$RoleObject.value) { 58 | Throw "Role $RoleDisplayName is not found." 59 | } 60 | $RoleGuids += ($RoleObject.value.id) 61 | } 62 | 63 | } 64 | Return $RoleGuids 65 | } 66 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-RoleGUIDtoDisplayName.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-RoleGUIDtoDisplayName { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-RoleGUIDtoDisplayName command uses a Token from the "Get-AccessToken" command to convert the [array]GUIDs of Roles to their DisplayNames of the Roletemplates as they exist in the targeted Tenant. 5 | 6 | .Description 7 | The command fills an object with the Role Template objects as found in the target directory. It then cycles through each Role displayName and attempts to match The displayName from parameter to the DisplayName 8 | of the Role Template Object. If there is a match, the GUID is added for the conversion. If there is no match the command looks at directory roles to look for a match based on displayName. 9 | If there is a match, the GUID is added for the conversion. 10 | 11 | Prerequisites 12 | - Valid Access Token with the minimum following API permissions: 13 | RoleManagement.Read.Directory 14 | 15 | .Example 16 | [array]$RoleGuids = "xxxx-xxxx-xxxxx-xxxxx" 17 | ConvertFrom-RoleGUIDtoDisplayName -RoleGuid $RoleGuids -Force $true -AccessToken $AccessToken 18 | #> 19 | Param 20 | ( 21 | [Parameter(Mandatory = $false)] 22 | [array]$RoleGuids, 23 | [Parameter(Mandatory = $true)] 24 | $AccessToken 25 | ) 26 | #Empty role DisplayName array 27 | [array]$RoleDisplayNames = $null 28 | 29 | #Get RoleTemplate Objects from Graph 30 | $URI = "https://graph.microsoft.com/beta/directoryRoletemplates" 31 | $RoleTemplates = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 32 | Start-Sleep -Seconds 1 33 | 34 | [array]$Roles = $RoleTemplates.value 35 | 36 | #For each in Policy File stated Role (DisplayName), attempt to map ObjectIDs based on Matching DisplayNames. 37 | Foreach ($RoleGuid in $RoleGuids) { 38 | 39 | #Find role in Default roles set. 40 | $Found = $Roles | Where-Object { $_.id -eq $RoleGuid } 41 | 42 | If ($Found) { 43 | $RoleDisplayNames += ($Found.displayName) 44 | } 45 | #if Roletemplate cant be found, check existing roles. 46 | Else { 47 | $URI = "https://graph.microsoft.com/beta/directoryRoles?" + '$filter' + "=displayName eq '$RoleDisplayName'" 48 | $RoleObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 49 | Start-Sleep -Seconds 1 50 | 51 | If (!$RoleObject.value) { 52 | Throw "Role $RoleGuid is not found as RoleTemplate or DirectoryRole." 53 | } 54 | $RoleDisplayNames += ($RoleObject.value.displayName) 55 | } 56 | 57 | } 58 | Return $RoleDisplayNames 59 | } 60 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-UserDisplayNameToGUID.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-UserDisplayNameToGUID { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-UserDisplayNameToGUID command uses a Token from the "Get-AccessToken" command to convert the [array]DisplayNames of Users to their GUIDs as they exist in the targeted Tenant. 5 | 6 | .Description 7 | 8 | .Description 9 | The command takes the array of DisplayNames of Users from the input in the parameter and checks their existence in the targeted AzureAD tenant. If the UserDisplayName Exists the GUID is returned 10 | And added to the UserGUIDs array. 11 | 12 | Prerequisites 13 | - Valid Access Token with the minimum following API permissions: 14 | Users.Read.All 15 | 16 | 17 | .Example 18 | [array]$UserDisplayNames = "william@fortigi.nl" 19 | ConvertFrom-UserDisplayNameGUID -UserDisplayNames $UserDisplayNames -AccessToken $AccessToken 20 | #> 21 | 22 | Param 23 | ( 24 | [Parameter(Mandatory = $false)] 25 | [array]$UserDisplayNames, 26 | [Parameter(Mandatory = $true)] 27 | $AccessToken 28 | ) 29 | 30 | [array]$UserGuids = $null 31 | 32 | Foreach ($UserDisplayName in $UserDisplayNames) { 33 | 34 | If ($UserDisplayName.ToString().ToLower() -ne "all") { 35 | $URI = "https://graph.microsoft.com/beta/users?" + '$filter' + "=DisplayName eq '$UserDisplayName'" 36 | $UserObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 37 | Start-Sleep -Seconds 1 38 | 39 | If (!$UserObject.value) { 40 | Throw "User: $UserDisplayName specified in the Policy was not found in the directory. Create user, or update your policy." 41 | } 42 | if ($UserObject.count -gt 1){ 43 | Throw "More than one Object was found for $UserDisplayName" 44 | } 45 | $UserGuids += ($UserObject.value.id) 46 | } 47 | Else { 48 | $UserGuids = $null 49 | $UserGuids += "All" 50 | } 51 | } 52 | Return $UserGuids 53 | } 54 | 55 | -------------------------------------------------------------------------------- /ConditionalAccess/Private/ConvertFrom-UserGUIDToDisplayName.ps1: -------------------------------------------------------------------------------- 1 | function ConvertFrom-UserGUIDToDisplayName { 2 | <# 3 | .SYNOPSIS 4 | The ConvertFrom-UserGUIDToUserDisplayName command uses a Token from the "Get-AccessToken" command to convert the [array]UserGuids of Users to their DisplayNames as they exist in the targeted Tenant. 5 | 6 | .Description 7 | 8 | .Description 9 | The command takes the array of DisplayNames of Users from the input in the parameter and checks their existence in the targeted AzureAD tenant. If the UserDisplayName Exists the GUID is returned 10 | And added to the UserGUIDs array. 11 | 12 | Prerequisites 13 | - Valid Access Token with the minimum following API permissions: 14 | Users.Read.All 15 | 16 | 17 | .Example 18 | [array]$UserGuids = "william@fortigi.nl" 19 | ConvertFrom-UserGUIDToUserDisplayName -UserGuids $UserGuids -AccessToken $AccessToken 20 | #> 21 | 22 | Param 23 | ( 24 | [Parameter(Mandatory = $false)] 25 | [array]$UserGuids, 26 | [Parameter(Mandatory = $true)] 27 | $AccessToken 28 | ) 29 | 30 | [array]$UserDisplayNames = $null 31 | 32 | Foreach ($Userguid in $UserGuids) { 33 | 34 | If ($Userguid -match '(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$' ) { 35 | $URI = "https://graph.microsoft.com/beta/users/$Userguid" 36 | $UserObject = Invoke-RestMethod -Method Get -Uri $URI -Headers @{"Authorization" = "Bearer $AccessToken" } 37 | If (!$UserObject) { 38 | Throw "User: $Userguid specified in the Policy was not found in the directory. Create user, or update your policy." 39 | } 40 | $UserDisplayNames += ($UserObject.DisplayName) 41 | } 42 | Else { 43 | $UserDisplayNames = $null 44 | $UserDisplayNames += $Userguid.ToString() 45 | } 46 | 47 | } 48 | Return $UserDisplayNames 49 | } -------------------------------------------------------------------------------- /ConditionalAccess/Public/Deploy-ConditionalAccessPolicies.ps1: -------------------------------------------------------------------------------- 1 | Function Deploy-ConditionalAccessPolicies { 2 | Param ( 3 | [Parameter(Mandatory = $True)] 4 | [System.String]$ClientId, 5 | [Parameter(Mandatory = $True)] 6 | [System.String]$ClientSecret, 7 | [Parameter(Mandatory = $True)] 8 | [System.String]$TenantId, 9 | 10 | [Parameter(Mandatory = $True)] 11 | [System.String]$PolicyFileLocation, 12 | 13 | [Parameter(Mandatory = $True)] 14 | [System.String]$PathConvertFile, 15 | [Parameter(Mandatory = $True)] 16 | [System.String]$TargetTenantName, 17 | 18 | [Parameter(Mandatory = $true)] 19 | [System.Boolean]$TestOnly, 20 | 21 | [Parameter(Mandatory = $true)] 22 | [System.Boolean]$Overwrite, 23 | 24 | [Parameter(Mandatory = $true)] 25 | [System.Boolean]$RemoveExisting 26 | ) 27 | 28 | #Get a token 29 | $AccessToken = Get-AccessToken -ClientId $ClientId -TenantId $TenantId -ClientSecret $ClientSecret 30 | 31 | #Get Policies for this deployment 32 | $Policies = Get-ChildItem -Path $PolicyFileLocation 33 | Write-Host ("Found: "+ $Policies.Count +" to import.") 34 | 35 | #Get existing policies from tenant 36 | $ExistingPolicies = Get-ConditionalAccessPolicy -AccessToken $AccessToken -ConvertGUIDs $false 37 | Write-Host ("Found: "+ $ExistingPolicies.Count +" existing policies.") 38 | 39 | Foreach ($Policy in $Policies) { 40 | 41 | #Get policy displayname 42 | $PolicyJson = Get-Content -Raw -Path $Policy.FullName | ConvertFrom-Json 43 | $PolicyDisplayName = $PolicyJson.displayName 44 | 45 | #Determin policy already exists in tenant 46 | $Exists = $False 47 | $Exists = $ExistingPolicies | Where-Object { $_.displayName -eq $PolicyDisplayName } 48 | 49 | If ($Exists) { 50 | if ($TestOnly) { 51 | Set-ConditionalAccessPolicy -Id $Exists.id -AccessToken $AccessToken -PolicyFile $Policy.FullName -PathConvertFile $PathConvertFile -TargetTenantName $TargetTenantName -TestOnly $True 52 | } 53 | Else { 54 | If ($Overwrite) { 55 | Write-Host "Updating $PolicyDisplayName." -ForegroundColor Green 56 | Set-ConditionalAccessPolicy -Id $Exists.id -AccessToken $AccessToken -PolicyFile $Policy.FullName -PathConvertFile $PathConvertFile -TargetTenantName $TargetTenantName 57 | } 58 | else { 59 | Write-Warning -Message "Policy $PolicyDisplayName was ignored because the Overwrite parameter was set to false" 60 | } 61 | } 62 | } 63 | Else { 64 | 65 | if ($TestOnly) { 66 | New-ConditionalAccessPolicy -AccessToken $AccessToken -PolicyFile $Policy.FullName -PathConvertFile $PathConvertFile -TargetTenantName $TargetTenantName -TestOnly $True 67 | } 68 | else { 69 | Write-Host "Creating $PolicyDisplayName." -ForegroundColor Green 70 | New-ConditionalAccessPolicy -AccessToken $AccessToken -PolicyFile $Policy.FullName -PathConvertFile $PathConvertFile -TargetTenantName $TargetTenantName 71 | } 72 | } 73 | } 74 | 75 | #Detect if policy is new or existing 76 | Foreach ($ExistingPolicy in $ExistingPolicies) { 77 | 78 | $Found = $false 79 | 80 | Foreach ($Policy in $Policies) { 81 | $PolicyJson = Get-Content -Raw -Path $Policy.FullName | ConvertFrom-Json 82 | $PolicyDisplayName = $PolicyJson.displayName 83 | 84 | if ($ExistingPolicy.displayName -eq $PolicyDisplayName) { 85 | $Found = $True 86 | } 87 | } 88 | 89 | If ($Found -eq $false) { 90 | 91 | $DisplayName = $ExistingPolicy.displayname 92 | 93 | if ($RemoveExisting) { 94 | if ($TestOnly) { 95 | Write-Warning "Test Only was set. If run without testonly policy: $DisplayName will be removed because it is not part of the deployment set." 96 | } 97 | else { 98 | Write-Warning "Removing $DisplayName." 99 | Remove-ConditionalAccessPolicy -Id $ExistingPolicy.id -AccessToken $AccessToken 100 | } 101 | } 102 | Else { 103 | Write-Warning "Current setup contains $DisplayName that is not part of this deployment. Use RemoveExisting varabile to remove any policies that don't exist in the deployment setup." 104 | } 105 | } 106 | } 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /ConditionalAccess/Public/Get-AccessToken.ps1: -------------------------------------------------------------------------------- 1 | function Get-AccessToken { 2 | <# 3 | .SYNOPSIS 4 | The Get-AccessToken command uses an App-registration in Azure Active directory to retrieve an Access token which can then be used for the other commands the App is permitted. This includes but is not 5 | limited to reading, creating and updating Conditional Access Polcies. 6 | 7 | .Description 8 | Prerequisites 9 | - App registered in the target Azure Active Directory 10 | - Valid client secret of the App 11 | - The App needs to have at least the followwing Admin Consented API permissions to be used for Conditional Access policies*: 12 | User.Read.All 13 | Application.Read.All 14 | Group.Read.All 15 | Policy.Read.All 16 | Policy.ReadWrite.ConditionalAccess 17 | 18 | -Optional for automatic group creation 19 | Group.Create 20 | 21 | *If you want to use the Token for other purposes you can modify the permissions to your own requirements 22 | 23 | More info and source code; 24 | https://github.com/Fortigi/ConditionalAccess 25 | 26 | .example 27 | $AccessToken = Get-AccessToken -ClientID xxxx-xxxx-xxxx-xxxx -ClientSecret xxxxxxxxxxxxxxxx -TenantID xxxx-xxxx-xxxx-xxxx 28 | #> 29 | Param( 30 | [Parameter(Mandatory = $True)] 31 | [System.String]$ClientId, 32 | [Parameter(Mandatory = $True)] 33 | [System.String]$ClientSecret, 34 | [Parameter(Mandatory = $True)] 35 | [System.String]$TenantId 36 | ) 37 | 38 | $Body = @{client_id = $ClientID; client_secret = $ClientSecret; grant_type = "client_credentials"; resource = "https://graph.microsoft.com"; } 39 | $OAuthReq = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/token" -Body $Body 40 | $AccessToken = $OAuthReq.access_token 41 | If ($AccessToken) { 42 | Return $AccessToken 43 | } 44 | If (!$AccessToken) { 45 | Throw "Error retrieving Graph Access Token. Please validate parameter input for -ClientID, -ClientSecret and -TenantId and check API permissions of the (App Registration) client in AzureAD" 46 | } 47 | } -------------------------------------------------------------------------------- /ConditionalAccess/Public/Get-ConditionalAccessPolicy.ps1: -------------------------------------------------------------------------------- 1 | function Get-ConditionalAccessPolicy { 2 | <# 3 | .SYNOPSIS 4 | The Get-ConditionalAccessPolicy command uses a Token from the "Get-AccessToken" command to get some or all of the Conditional Access policies in the targeted AzureAD tenant. 5 | 6 | .Description 7 | The Get-ConditionalAccessPolicy command uses a Token from the "Get-AccessToken" command to get some or all of the Conditional Access policies in the targeted AzureAD tenant. Depending on the 8 | -ConvertGUIDs parameter, it will automatically convert the non-human readable GUIDs in Graph to human readable Displaynames. 9 | 10 | Prerequisites 11 | - App registered in the target Azure Active Directory 12 | - Valid client secret of the App 13 | - The App needs to have at least the followwing Admin Consented API permissions to be used for Conditional Access policies*: 14 | User.Read.All 15 | Application.Read.All 16 | Group.Read.All 17 | Policy.Read.All 18 | Policy.Read.ConditionalAccess 19 | 20 | More info and source code; 21 | https://github.com/Fortigi/ConditionalAccess 22 | 23 | .example 24 | #Example to get All policies 25 | Get-ConditionalAccessPolicy -AccessToken $AccessToken 26 | 27 | #Example to get a specIfic policy based on DisplayName 28 | $ConditionalAccessPolicyDisplayName = "CA-01- All Apps - All Admins - Require MFA" 29 | Get-ConditionalAccessPolicy -AccessToken $AccessToken -DisplayName $ConditionalAccessPolicyDisplayName 30 | #> 31 | [cmdletbinding()] 32 | Param 33 | ( 34 | [Parameter(Mandatory = $true)] 35 | $AccessToken, 36 | [Parameter(Mandatory = $false)] 37 | $Id = $false, 38 | [Parameter(Mandatory = $false)] 39 | $DisplayName = $false, 40 | [Parameter(Mandatory = $false)] 41 | $ConvertGUIDs = $True, 42 | [Parameter(Mandatory = $false)] 43 | $PathConvertFile 44 | ) 45 | 46 | If ($Id) { 47 | $conditionalAccessURI = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/{$Id}" 48 | } 49 | ElseIf ($DisplayName) { 50 | $conditionalAccessURI = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies?`$filter=endswith(displayName, '$DisplayName')" 51 | } 52 | Else { 53 | $conditionalAccessURI = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies" 54 | } 55 | $conditionalAccessPolicyResponse = Invoke-RestMethod -Method Get -Uri $conditionalAccessURI -Headers @{"Authorization" = "Bearer $AccessToken" } 56 | 57 | [Array]$Policies = $conditionalAccessPolicyResponse.value 58 | 59 | If ($ConvertGUIDs -eq $True) { 60 | 61 | #Guids from GUID to DisplayName 62 | Foreach ($Policy in $Policies) { 63 | [Array]$InclusionApplicationDisplayNames = ConvertFrom-ApplicationGUIDToDisplayName -ApplicationGuids ($Policy.conditions.applications.includeApplications) -AccessToken $AccessToken 64 | [Array]$ExclusionApplicationDisplayNames = ConvertFrom-ApplicationGUIDToDisplayName -ApplicationGuids ($Policy.conditions.applications.excludeApplications) -AccessToken $AccessToken 65 | [array]$InclusionUsersDisplayNames = ConvertFrom-UserGUIDToDisplayName -UserGUIDs ($Policy.conditions.users.includeUsers) -AccessToken $AccessToken 66 | [array]$ExclusionUsersDisplayNames = ConvertFrom-UserGUIDToDisplayName -UserGUIDs ($Policy.conditions.users.ExcludeUsers) -AccessToken $AccessToken 67 | [array]$InclusionGroupsDisplayNames = ConvertFrom-GroupGUIDToDisplayName -GroupGuids ($Policy.conditions.users.includeGroups) -AccessToken $AccessToken 68 | [array]$ExclusionGroupsDisplayNames = ConvertFrom-GroupGUIDToDisplayName -GroupGuids ($Policy.conditions.users.excludeGroups) -AccessToken $AccessToken 69 | [array]$InclusionRoleDisplayNames = ConvertFrom-RoleGUIDtoDisplayName -RoleGuids ($Policy.conditions.users.includeRoles) -AccessToken $AccessToken 70 | [array]$ExclusionRoleDisplayNames = ConvertFrom-RoleGUIDtoDisplayName -RoleGuids ($Policy.conditions.users.excludeRoles) -AccessToken $AccessToken 71 | [array]$InclusionLocationDisplayNames = ConvertFrom-LocationGUIDToDisplayName -LocationGuids ($Policy.conditions.locations.includeLocations) -AccessToken $AccessToken 72 | [array]$ExclusionLocationDisplayNames = ConvertFrom-LocationGUIDtoDisplayName -LocationGuids ($Policy.conditions.locations.excludeLocations) -AccessToken $AccessToken 73 | [array]$AgreementDisplayNames = ConvertFrom-AgreementGUIDToDisplayName -AgreementGuids ($Policy.grantControls.termsOfUse) -AccessToken $AccessToken -PathConvertFile $PathConvertFile 74 | 75 | 76 | If ($InclusionApplicationDisplayNames) { 77 | $Policy.conditions.applications.includeApplications = $InclusionApplicationDisplayNames 78 | } 79 | If ($ExclusionApplicationDisplayNames) { 80 | $Policy.conditions.applications.excludeApplications = $ExclusionApplicationDisplayNames 81 | } 82 | If ($InclusionUsersDisplayNames) { 83 | $Policy.conditions.users.includeUsers = $InclusionUsersDisplayNames 84 | } 85 | If ($ExclusionUsersDisplayNames) { 86 | $Policy.conditions.users.ExcludeUsers = $ExclusionUsersDisplayNames 87 | } 88 | If ($InclusionGroupsDisplayNames) { 89 | $Policy.conditions.users.includeGroups = $InclusionGroupsDisplayNames 90 | } 91 | If ($ExclusionGroupsDisplayNames) { 92 | $Policy.conditions.users.excludeGroups = $ExclusionGroupsDisplayNames 93 | } 94 | If ($InclusionRoleDisplayNames) { 95 | $Policy.conditions.users.includeRoles = $InclusionRoleDisplayNames 96 | } 97 | If ($ExclusionRoleDisplayNames) { 98 | $Policy.conditions.users.excludeRoles = $ExclusionRoleDisplayNames 99 | } 100 | If ($InclusionLocationDisplayNames) { 101 | $Policy.conditions.locations.includeLocations = $InclusionLocationDisplayNames 102 | } 103 | If ($ExclusionLocationDisplayNames) { 104 | $Policy.conditions.locations.excludeLocations = $ExclusionLocationDisplayNames 105 | } 106 | If ($AgreementDisplayNames) { 107 | $Policy.grantControls.termsOfUse = $AgreementDisplayNames 108 | } 109 | 110 | } 111 | 112 | #Role GUIDs to DisplayName 113 | 114 | 115 | } 116 | 117 | return $Policies 118 | 119 | 120 | 121 | } 122 | 123 | -------------------------------------------------------------------------------- /ConditionalAccess/Public/Get-ConditionalAccessPolicyFile.ps1: -------------------------------------------------------------------------------- 1 | function Get-ConditionalAccessPolicyFile { 2 | [cmdletbinding()] 3 | Param 4 | ( 5 | [Parameter(Mandatory = $true)] 6 | $AccessToken, 7 | [Parameter(Mandatory = $true)] 8 | $Path, 9 | [Parameter(Mandatory = $false)] 10 | $Id = $false, 11 | [Parameter(Mandatory = $false)] 12 | $DisplayName = $false, 13 | [Parameter(Mandatory = $false)] 14 | $ConvertGUIDs = $true, 15 | [Parameter(Mandatory = $false)] 16 | $PathConvertFile 17 | 18 | ) 19 | 20 | [Array]$Policies = Get-ConditionalAccessPolicy -AccessToken $AccessToken -DisplayName $DisplayName -Id $Id -ConvertGUIDs $ConvertGUIDs -PathConvertFile $PathConvertFile 21 | 22 | Foreach ($Policy in $Policies) { 23 | 24 | #Check for characters that can't be used in filenames 25 | $FileName = ($Policy.displayName + ".json").Replace(":","").Replace("\","").Replace("*","").Replace("<","").Replace(">","") 26 | $Json = $Policy | ConvertTo-Json -Depth 3 27 | $Json | Out-file ($Path + "\" + $FileName) 28 | } 29 | } -------------------------------------------------------------------------------- /ConditionalAccess/Public/New-ConditionalAccessPolicy.ps1: -------------------------------------------------------------------------------- 1 | function New-ConditionalAccessPolicy { 2 | <# 3 | .SYNOPSIS 4 | The New-ConditionalAccessPolicy command uses a Token from the "Get-AccessToken" command to create a new Conditional Access Policy, using a .JSON file as input. 5 | 6 | .Description 7 | The command takes the content of the JSON file and converts it to an Powershell Object so that the data in the JSON can be correctly translated to input accepted by Graph. 8 | It is possible to add a file containing the Policy directly through the -PoliyFile parameter. The script will automatically convert the file to a JSON object. 9 | 10 | In order to allow for more flexibility rolling out the exact same JSONS to dIfferent Tenants while maintaining the readability of the JSON policy files: 11 | - The "DisplayNames" of "Groups", Roles and "Applications" are automatically translated to their respective ObjectIDs (GUIDs) as they are found in the targeted Tenant in the background. 12 | - The "DisplayName" of "Users" are automatically translated to their respective ObjectIDs (GUIDs) as they are found the targeted Tenant in the background. 13 | 14 | The -CreateMissingGroups Paramter can be added to automatically create "Groups" based on the displayNames found in the JSON If no correlating "Groups" are found in the target tenant. 15 | 16 | Prerequisites 17 | - Valid Access Token with the minimum following API permissions: 18 | User.Read.All 19 | Application.Read.All 20 | Group.Read.All 21 | Policy.Read.All 22 | Policy.ReadWrite.ConditionalAccess 23 | RoleManagement.Read.Directory 24 | The Command automatically converts existing DisplayNames from the JSON to their ObjectIDs (GUIDs) in the targeted Tenant. 25 | 26 | -Optional permission for automatic group creation 27 | Group.Create 28 | 29 | More info and source code; 30 | https://github.com/Fortigi/ConditionalAccess 31 | 32 | .Example 33 | #Create a new Conditional Access Policy 34 | $PolicyJson = Get-content -path -raw 35 | New-ConditionalAccessPolicy -PolicyJson $PolicyJson -AccessToken $AccessToken 36 | 37 | #Deploy a policy set and create any non existing groups 38 | $PolicyFiles = get-childitem 39 | foreach { 40 | New-ConditionalAccessPolicy -AccessToken $AccessToken -PolicyFile $PolicyFile -Force $True 41 | } 42 | #> 43 | 44 | [cmdletbinding()] 45 | Param 46 | ( 47 | [parameter(Mandatory = $true, Position = 0, ParameterSetName = "PolicyJson")] 48 | [parameter(Mandatory = $true, Position = 0, ParameterSetName = "PolicyFile")] 49 | 50 | [Parameter(Mandatory = $true, Position = 0)] 51 | $AccessToken, 52 | 53 | [Parameter(Mandatory = $true, ParameterSetName = "PolicyJson")] 54 | [ValidateNotNullOrEmpty()] 55 | $PolicyJson, 56 | 57 | [Parameter(Mandatory = $true, ParameterSetName = "PolicyFile")] 58 | [ValidateNotNullOrEmpty()] 59 | [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] 60 | $PolicyFile, 61 | 62 | [Parameter(Mandatory = $False)] 63 | [System.Boolean]$CreateMissingGroups, 64 | 65 | [Parameter(Mandatory = $False)] 66 | [System.Boolean]$TestOnly = $False, 67 | 68 | [Parameter(Mandatory = $False)] 69 | $PathConvertFile, 70 | 71 | [Parameter(Mandatory = $False)] 72 | $TargetTenantName 73 | ) 74 | 75 | If ($PathConvertFile) { 76 | If (!($TargetTenantName)) { 77 | Throw "When specifying a ConvertFile you also need to specify the TargetTenantName variable." 78 | } 79 | } 80 | 81 | If ($PolicyFile) { 82 | $PolicyJson = Get-content -path $PolicyFile -raw 83 | } 84 | 85 | if ($TestOnly -eq $True) { 86 | if ($CreateMissingGroups -eq $true) { 87 | Throw "Combination of CreateMissingGroup Parameter and TestOnly Parameter cannot both be true." 88 | } 89 | 90 | } 91 | #Convert JSON to Powershell 92 | $PolicyPS = $PolicyJson | convertFrom-Json 93 | 94 | #Get GUIDs for the DisplayNames of the Groups from the Powershell-representation of the JSON, from AzureAD through use of Microsoft Graph. 95 | [array]$InclusionGroupsGuids = ConvertFrom-GroupDisplayNameToGUID -GroupDisplayNames ($PolicyPs.conditions.users.includeGroups) -AccessToken $AccessToken -CreateMissingGroups $CreateMissingGroups 96 | [array]$ExclusionGroupsGuids = ConvertFrom-GroupDisplayNameToGUID -GroupDisplayNames ($PolicyPs.conditions.users.excludeGroups) -AccessToken $AccessToken -CreateMissingGroups $CreateMissingGroups 97 | #Get GUIDs for the DisplayName of the Users from the Powershell representation of the JSON, from AzureAD through use of Microsoft Graph. 98 | [array]$InclusionUsersGuids = ConvertFrom-UserDisplayNameToGUID -UserDisplayNames ($PolicyPs.conditions.users.includeUsers) -AccessToken $AccessToken 99 | [array]$ExclusionUsersGuids = ConvertFrom-UserDisplayNameToGUID -UserDisplayNames ($PolicyPs.conditions.users.ExcludeUsers) -AccessToken $AccessToken 100 | #Get GUIDs for the DisplayName of the Application from the Powershell representation of the JSON, from AzureAD through use of Microsoft Graph. 101 | [array]$InclusionApplicationGuids = ConvertFrom-ApplicationDisplayNametoGUID -ApplicationDisplayNames ($PolicyPs.conditions.applications.includeApplications) -AccessToken $AccessToken 102 | [array]$ExclusionApplicationGuids = ConvertFrom-ApplicationDisplayNametoGUID -ApplicationDisplayNames ($PolicyPs.conditions.applications.excludeApplications) -AccessToken $AccessToken 103 | #Get GUIDs for the DisplayName of the Roles from the Powershell representation of the JSON, from AzureAD through use of Microsoft Graph. 104 | [array]$InclusionRoleGuids = ConvertFrom-RoleDisplayNametoGUID -RoleDisplayNames ($PolicyPs.conditions.users.includeRoles) -AccessToken $AccessToken 105 | [array]$ExclusionRoleGuids = ConvertFrom-RoleDisplayNametoGUID -RoleDisplayNames ($PolicyPs.conditions.users.excludeRoles) -AccessToken $AccessToken 106 | #Get GUIDs for the DisplayName of the Locations from the Powershell representation of the JSON, from AzureAD through the use of Microsoft Graph. 107 | [array]$InclusionLocationGuids = ConvertFrom-LocationDisplayNameToGUID -LocationDisplayNames ($PolicyPs.conditions.locations.includeLocations) -AccessToken $AccessToken 108 | [array]$ExclusionLocationGuids = ConvertFrom-LocationDisplayNameToGUID -LocationDisplayNames ($PolicyPs.conditions.locations.ExcludeLocations) -AccessToken $AccessToken 109 | #Get GUIds for the DisplayName of TermsofUse (Agreement-object) in the targeted tenant. The Convert.Json file to function since Graph does not support this functionality yet. 110 | [array]$AgreementGuids = ConvertFrom-AgreementDisplayNameToGUID -AgreementDisplayNames ($PolicyPS.grantControls.termsOfUse) -AccessToken $AccessToken -PathConvertFile $PathConvertFile -TargetTenantName $TargetTenantName 111 | 112 | 113 | #Convert the Displaynames in the Powershell-object to the GUIDs. 114 | If ($InclusionGroupsGuids) { 115 | $PolicyPs.conditions.users.includeGroups = $InclusionGroupsGuids 116 | } 117 | If ($ExclusionGroupsGuids) { 118 | $PolicyPs.conditions.users.excludeGroups = $ExclusionGroupsGuids 119 | } 120 | If ($InclusionUsersGuids) { 121 | $PolicyPs.conditions.users.includeUsers = $InclusionUsersGuids 122 | } 123 | If ($ExclusionUsersGuids) { 124 | $PolicyPs.conditions.users.ExcludeUsers = $ExclusionUsersGuids 125 | } 126 | If ($inclusionApplicationGuids) { 127 | $PolicyPs.conditions.applications.includeApplications = $InclusionApplicationGuids 128 | } 129 | If ($ExclusionApplicationGuids) { 130 | $PolicyPs.conditions.applications.excludeApplications = $ExclusionApplicationGuids 131 | } 132 | If ($InclusionRoleGuids) { 133 | $PolicyPs.conditions.users.includeRoles = $InclusionRoleGuids 134 | } 135 | If ($ExclusionRoleGuids) { 136 | $PolicyPs.conditions.users.excludeRoles = $ExclusionRoleGuids 137 | } 138 | If ($InclusionLocationGuids) { 139 | $PolicyPs.conditions.locations.includeLocations = $InclusionLocationGuids 140 | } 141 | If ($ExclusionLocationGuids) { 142 | $PolicyPs.conditions.locations.excludeLocations = $ExclusionLocationGuids 143 | } 144 | If ($AgreementGuids) { 145 | $PolicyPS.grantControls.termsOfUse = $AgreementGuids 146 | } 147 | 148 | 149 | #If ID and creation date, set to null 150 | If ($PolicyPs.id) { 151 | $policyPS.psobject.Properties.remove('id') 152 | } 153 | if ($PolicyPs.createdDateTime) { 154 | $PolicyPS.createdDateTime = $null 155 | } 156 | if ($PolicyPs.modifiedDateTime) { 157 | $PolicyPS.modifiedDateTime = $null 158 | } 159 | 160 | #Converts Powershell-Object with new Configuration back to Json 161 | $ConvertedPolicyJson = $PolicyPS | ConvertTo-Json -depth 3 162 | #Create new Policy using Graph 163 | If ($TestOnly -eq $False) { 164 | $conditionalAccessURI = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies" 165 | $conditionalAccessPolicyResponse = Invoke-RestMethod -Method Post -Uri $conditionalAccessURI -Headers @{"Authorization" = "Bearer $AccessToken" } -Body $ConvertedPolicyJson -ContentType "application/json" 166 | $conditionalAccessPolicyResponse 167 | } 168 | Else { 169 | Write-Host ("TestOnly was set, Policy: " + $PolicyPs.displayName + " was not created. If no error was shown, Policy would have been succesfully created.") -ForegroundColor Green 170 | } 171 | } 172 | 173 | 174 | -------------------------------------------------------------------------------- /ConditionalAccess/Public/Remove-ConditionalAccessPolicy.ps1: -------------------------------------------------------------------------------- 1 | function Remove-ConditionalAccessPolicy { 2 | <# 3 | .SYNOPSIS 4 | The Remove-ConditionalAccessPolicy command uses a Token from the "Get-AccessToken" command to remove an existing Conditional Access Policy, using an ID as input for the targeted policy. 5 | 6 | .Description 7 | The command removes an existing Conditional Access Policy Based on the ID of the Policy 8 | 9 | Prerequisites 10 | - Valid Access Token with the minimum following API permissions: 11 | User.Read.All 12 | Application.Read.All 13 | Group.Read.All 14 | Policy.Read.All 15 | Policy.ReadWrite.ConditionalAccess 16 | RoleManagement.Read.Directory 17 | 18 | - An existing policy to delete 19 | Policy ID required for this command can be found using the "Get-ConditionalAccessPolicy" command 20 | #> 21 | [cmdletbinding()] 22 | Param 23 | ( 24 | [Parameter(Mandatory = $true)] 25 | $Id, 26 | [Parameter(Mandatory = $true)] 27 | $AccessToken 28 | ) 29 | $conditionalAccessURI = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/{$Id}" 30 | $conditionalAccessPolicyResponse = Invoke-RestMethod -Method Delete -Uri $conditionalAccessURI -Headers @{"Authorization" = "Bearer $AccessToken" } 31 | $conditionalAccessPolicyResponse 32 | } -------------------------------------------------------------------------------- /ConditionalAccess/Public/Set-ConditionalAccessPolicy.ps1: -------------------------------------------------------------------------------- 1 | function Set-ConditionalAccessPolicy { 2 | <# 3 | .SYNOPSIS 4 | The Set-ConditionalAccessPolicy command uses a Token from the "Get-AccessToken" command to update a new Conditional Access Policy, using a .JSON file as input. 5 | 6 | .Description 7 | The command takes the content of the JSON file and converts it to an Powershell Object so that the data in the JSON can be correctly translated to input accepted by Graph. 8 | 9 | In order to allow for more flexibility rolling out the exact same JSONS to different Tenants while maintaining the readability of the JSON policy files: 10 | - The "DisplayNames" of "Groups", Users and "Applications" in the JSON are automatically translated to their respective ObjectIDs (GUIDs) as they are found in the targeted Tenant in the background. 11 | 12 | The -CreateMissingGroups Paramter can be set to $true to automatically create "Groups" based on the displayNames found in the JSON if no correlating "Groups" are found in the target tenant. 13 | 14 | Prerequisites 15 | - Valid Access Token with the minimum following API permissions: 16 | User.Read.All 17 | Application.Read.All 18 | Group.Read.All 19 | Policy.Read.All 20 | Policy.ReadWrite.ConditionalAccess 21 | RoleManagement.Read.Directory 22 | The Command automatically converts existing DisplayNames from the JSON to their ObjectIDs (GUIDs) in the targeted Tenant. 23 | 24 | -Optional permission for automatic group creation 25 | Group.Create 26 | 27 | More info and source code; 28 | https://github.com/Fortigi/ConditionalAccess 29 | 30 | #> 31 | 32 | [cmdletbinding()] 33 | Param 34 | ( 35 | [parameter(Mandatory = $true, Position = 0, ParameterSetName = "PolicyJson")] 36 | [parameter(Mandatory = $true, Position = 0, ParameterSetName = "PolicyFile")] 37 | 38 | [Parameter(Mandatory = $true, Position = 0)] 39 | $AccessToken, 40 | 41 | [Parameter(Mandatory = $true, ParameterSetName = "PolicyJson")] 42 | [ValidateNotNullOrEmpty()] 43 | $PolicyJson, 44 | 45 | [Parameter(Mandatory = $true, ParameterSetName = "PolicyFile")] 46 | [ValidateNotNullOrEmpty()] 47 | [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] 48 | $PolicyFile, 49 | 50 | [Parameter(Mandatory = $true)] 51 | $Id, 52 | 53 | [Parameter(Mandatory = $False)] 54 | [System.Boolean]$TestOnly = $False, 55 | 56 | [Parameter(Mandatory = $False)] 57 | $PathConvertFile, 58 | [Parameter(Mandatory = $False)] 59 | $TargetTenantName, 60 | [Parameter(Mandatory = $False)] 61 | [System.Boolean]$CreateMissingGroups 62 | ) 63 | 64 | If ($PolicyFile) { 65 | $PolicyJson = Get-content -path $PolicyFile -raw 66 | } 67 | 68 | If ($PathConvertFile) { 69 | If (!($TargetTenantName)) { 70 | Throw "When specifying a ConvertFile you also need to specify the TargetTenantName variable." 71 | } 72 | } 73 | 74 | #Convert JSON to Powershell 75 | $PolicyPS = $PolicyJson | convertFrom-Json 76 | 77 | #Get GUIDs for the DisplayNames of the Groups from the Powershell-representation of the JSON, from AzureAD through use of Microsoft Graph. 78 | [array]$InclusionGroupsGuids = ConvertFrom-GroupDisplayNameToGUID -GroupDisplayNames ($PolicyPs.conditions.users.includeGroups) -AccessToken $AccessToken -CreateMissingGroups $CreateMissingGroups 79 | [array]$ExclusionGroupsGuids = ConvertFrom-GroupDisplayNameToGUID -GroupDisplayNames ($PolicyPs.conditions.users.excludeGroups) -AccessToken $AccessToken -CreateMissingGroups $CreateMissingGroups 80 | #Get GUIDs for the DisplayName of the Users from the Powershell representation of the JSON, from AzureAD through use of Microsoft Graph. 81 | [array]$InclusionUsersGuids = ConvertFrom-UserDisplayNameToGUID -UserDisplayNames ($PolicyPs.conditions.users.includeUsers) -AccessToken $AccessToken 82 | [array]$ExclusionUsersGuids = ConvertFrom-UserDisplayNameToGUID -UserDisplayNames ($PolicyPs.conditions.users.ExcludeUsers) -AccessToken $AccessToken 83 | #Get GUIDs for the DisplayName of the Application from the Powershell representation of the JSON, from AzureAD through use of Microsoft Graph. 84 | [array]$InclusionApplicationGuids = ConvertFrom-ApplicationDisplayNametoGUID -ApplicationDisplayNames ($PolicyPs.conditions.applications.includeApplications) -AccessToken $AccessToken 85 | [array]$ExclusionApplicationGuids = ConvertFrom-ApplicationDisplayNametoGUID -ApplicationDisplayNames ($PolicyPs.conditions.applications.excludeApplications) -AccessToken $AccessToken 86 | #Get GUIDs for the DisplayName of the Roles from the Powershell representation of the JSON, from AzureAD through use of Microsoft Graph. 87 | [array]$InclusionRoleGuids = ConvertFrom-RoleDisplayNametoGUID -RoleDisplayNames ($PolicyPs.conditions.users.includeRoles) -AccessToken $AccessToken 88 | [array]$ExclusionRoleGuids = ConvertFrom-RoleDisplayNametoGUID -RoleDisplayNames ($PolicyPs.conditions.users.excludeRoles) -AccessToken $AccessToken 89 | #Get GUIDs for the DisplayName of the Locations from the Powershell representation of the JSON, from AzureAD through the use of Microsoft Graph. 90 | [array]$InclusionLocationGuids = ConvertFrom-LocationDisplayNameToGUID -LocationDisplayNames ($PolicyPs.conditions.locations.includeLocations) -AccessToken $AccessToken 91 | [array]$ExclusionLocationGuids = ConvertFrom-LocationDisplayNameToGUID -LocationDisplayNames ($PolicyPs.conditions.locations.ExcludeLocations) -AccessToken $AccessToken 92 | #Get GUIds for the DisplayName of TermsofUse (Agreement-object) in the targeted tenant. The Convert.Json file to function since Graph does not support this functionality yet. 93 | [array]$AgreementGuids = ConvertFrom-AgreementDisplayNameToGUID -AgreementDisplayNames ($PolicyPS.grantControls.termsOfUse) -AccessToken $AccessToken -PathConvertFile $PathConvertFile -TargetTenantName $TargetTenantName 94 | 95 | 96 | #Convert the Displaynames in the Powershell-object to the GUIDs. 97 | If ($InclusionGroupsGuids) { 98 | $PolicyPs.conditions.users.includeGroups = $InclusionGroupsGuids 99 | } 100 | If ($ExclusionGroupsGuids) { 101 | $PolicyPs.conditions.users.excludeGroups = $ExclusionGroupsGuids 102 | } 103 | If ($InclusionUsersGuids) { 104 | $PolicyPs.conditions.users.includeUsers = $InclusionUsersGuids 105 | } 106 | If ($ExclusionUsersGuids) { 107 | $PolicyPs.conditions.users.ExcludeUsers = $ExclusionUsersGuids 108 | } 109 | If ($inclusionApplicationGuids) { 110 | $PolicyPs.conditions.applications.includeApplications = $InclusionApplicationGuids 111 | } 112 | If ($ExclusionApplicationGuids) { 113 | $PolicyPs.conditions.applications.excludeApplications = $ExclusionApplicationGuids 114 | } 115 | If ($InclusionRoleGuids) { 116 | $PolicyPs.conditions.users.includeRoles = $InclusionRoleGuids 117 | } 118 | If ($ExclusionRoleGuids) { 119 | $PolicyPs.conditions.users.excludeRoles = $ExclusionRoleGuids 120 | } 121 | If ($InclusionLocationGuids) { 122 | $PolicyPs.conditions.locations.includeLocations = $InclusionLocationGuids 123 | } 124 | If ($ExclusionLocationGuids) { 125 | $PolicyPs.conditions.locations.excludeLocations = $ExclusionLocationGuids 126 | } 127 | If ($AgreementGuids) { 128 | $PolicyPS.grantControls.termsOfUse = $AgreementGuids 129 | } 130 | 131 | #If ID and creation date, set to null 132 | If ($PolicyPs.id) { 133 | $policyPS.id = $Id 134 | } 135 | if ($PolicyPs.createdDateTime) { 136 | $PolicyPS.createdDateTime = $null 137 | } 138 | if ($PolicyPs.modifiedDateTime) { 139 | $PolicyPS.modifiedDateTime = $null 140 | } 141 | 142 | #Converts Powershell-Object with new Configuration back to Json 143 | $ConvertedPolicyJson = $PolicyPS | ConvertTo-Json -depth 3 144 | 145 | If ($TestOnly -eq $False) { 146 | $ConditionalAccessURI = "https://graph.microsoft.com/beta/identity/conditionalAccess/policies/{$Id}" 147 | $ConditionalAccessPolicyResponse = Invoke-RestMethod -Method Patch -Uri $conditionalAccessURI -Headers @{"Authorization" = "Bearer $AccessToken" } -Body $ConvertedPolicyJson -ContentType "application/json" 148 | $ConditionalAccessPolicyResponse 149 | } 150 | Else { 151 | Write-Host ("TestOnly was set, Policy: " + $PolicyPs.displayName + " was not updated. If no error was shown, Policy would have been succesfully updated.") -ForegroundColor Green 152 | } 153 | } -------------------------------------------------------------------------------- /Examples/Policy/CA-01- All Apps - All Admins - Require MFA.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "CA-01- All Apps - All Admins - Require MFA", 3 | "createdDateTime": null, 4 | "modifiedDateTime": null, 5 | "state": "disabled", 6 | "sessionControls": null, 7 | "conditions": { 8 | "signInRiskLevels": [], 9 | "clientAppTypes": ["All"], 10 | "platforms": null, 11 | "locations": null, 12 | "deviceStates": null, 13 | "applications": { 14 | "includeApplications": [ 15 | "Office 365", 16 | "Microsoft Azure Management" 17 | ], 18 | "excludeApplications": [], 19 | "includeUserActions": [] 20 | }, 21 | "users": { 22 | "includeUsers": ["William Overweg"], 23 | "excludeUsers": [], 24 | "includeGroups": [ 25 | "CA-01 - Inclusion5099", 26 | "CA-01 - Inclusion6099" 27 | ], 28 | "excludeGroups": ["CA-01 - Exclusion"], 29 | "includeRoles": ["All"], 30 | "excludeRoles": [] 31 | } 32 | }, 33 | "grantControls": { 34 | "operator": "OR", 35 | "builtInControls": [ 36 | "mfa" 37 | ], 38 | "customAuthenticationFactors": [], 39 | "termsOfUse": [] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Examples/Policy/CA-02- All Apps - All Users - Require MFA or Trusted Device.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "CA-02- All Apps - All Users - Require MFA or Trusted Device", 3 | "createdDateTime": null, 4 | "modifiedDateTime": null, 5 | "state": "disabled", 6 | "sessionControls": null, 7 | "conditions": { 8 | "signInRiskLevels": [], 9 | "clientAppTypes": ["All"], 10 | "platforms": null, 11 | "deviceStates": null, 12 | "applications": { 13 | "includeApplications": [ 14 | "All" 15 | ], 16 | "excludeApplications": [], 17 | "includeUserActions": [] 18 | }, 19 | "users": { 20 | "includeUsers": [ 21 | "william@fortigi.nl" 22 | ], 23 | "excludeUsers": [], 24 | "includeGroups": [], 25 | "excludeGroups": [], 26 | "includeRoles": ["Helpdesk Administrator", 27 | "Search Editor"], 28 | "excludeRoles": [] 29 | }, 30 | "locations": null 31 | }, 32 | "grantControls": { 33 | "operator": "OR", 34 | "builtInControls": [ 35 | "mfa", 36 | "compliantDevice", 37 | "domainJoinedDevice" 38 | ], 39 | "customAuthenticationFactors": [], 40 | "termsOfUse": [] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/Policy/Convert.Json: -------------------------------------------------------------------------------- 1 | { 2 | "TermsOfUse": [ 3 | { 4 | "DisplayName": "YourTermsofUse", 5 | "Tenant": [ 6 | { 7 | "TenantName": "YourTenant51.onmicrosoft.com", 8 | "TermsOfUseObjectID": "71cfc9a9-819c-4ae2-9e90-85a1c83e7554" 9 | }, 10 | { 11 | "TenantName": "YourOtherTenant52.onmicrosoft.com", 12 | "TermsOfUseObjectID": "c13852f1-9411-4759-a13b-9859dc604d94" 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Examples/Policy/Temp/CA-01- All Apps - All Admins - Require MFA.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fortigi/ConditionalAccess/1eece30d02903ea58ec6e3608bba4085f55189e0/Examples/Policy/Temp/CA-01- All Apps - All Admins - Require MFA.json -------------------------------------------------------------------------------- /Examples/Policy/Temp/CA-02- All Apps - All Admins - Require MFA.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fortigi/ConditionalAccess/1eece30d02903ea58ec6e3608bba4085f55189e0/Examples/Policy/Temp/CA-02- All Apps - All Admins - Require MFA.json -------------------------------------------------------------------------------- /Examples/Script/Demo.ps1: -------------------------------------------------------------------------------- 1 | #Prerequisites a ClientID + ClientSecret with the minimum following API permissions: 2 | # User.Read.All 3 | # Application.Read.All 4 | # Group.Read.All 5 | # Policy.Read.All 6 | # Policy.ReadWrite.ConditionalAccess 7 | # RoleManagement.Read.Directory 8 | 9 | # -Optional permission for automatic group creation 10 | # Group.Create 11 | 12 | ################################################################################################################################ 13 | #Load module and get access token 14 | ################################################################################################################################ 15 | 16 | Import-Module .\ConditionalAccess\ConditionalAccess.psm1 -Force 17 | 18 | 19 | #PorIAMDEV 20 | $TargetTenantName = "xxxxx.onmicrosoft.com" 21 | $TenantId = "xxxxxxx" 22 | $ClientId = "xxxxxxxx" 23 | $ClientSecret = "xxxxxxxxx" 24 | 25 | 26 | $PathConvertFile = ".\Examples\Policy\Convert.Json" 27 | $PolicyFileLocation = ".\Examples\Policy\Policies" 28 | 29 | #Get Policies from Azure 30 | $AccessToken = Get-AccessToken -ClientId $ClientId -TenantId $TenantId -ClientSecret $ClientSecret 31 | Get-ConditionalAccessPolicyFile -AccessToken $AccessToken -Path $PolicyFileLocation -PathConvertFile $PathConvertFile 32 | 33 | #Deploy Policies to Azure 34 | Deploy-ConditionalAccessPolicies -TargetTenantName $TargetTenantName ` 35 | -TenantId $TenantId ` 36 | -ClientId $ClientId ` 37 | -ClientSecret $ClientSecret ` 38 | -PolicyFileLocation $PolicyFileLocation ` 39 | -PathConvertFile $PathConvertFile ` 40 | -TestOnly $true ` 41 | -Overwrite $true ` 42 | -RemoveExisting $true 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fortigi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConditionalAccess 2 | 3 | This solution consists of powershell functions, bundled in a module to help automate and regulate Conditional Access deployment and maintance. 4 | 5 | ## The module 6 | The module consists of several public functions and several private functions, or sub-functions. 7 | 8 | ### The public functions are: 9 | - Get-AccessToken (to retrieve an access token used for accessing Microsoft Graph) 10 | - Get-ConditionalAccessPolicy (to retrieve one or multiple Conditional Access Policies) 11 | - Get-ConditionalAcessPolicyFile (to retrieve one or multiple Conditional Access Policies with a .Json file as output) 12 | - New-ConditionalAccessPolicy (to create a new Conditional Access Policy using a .Json file as input) 13 | - Remove-ConditionalAccessPolicy (to remove an existing Conditional Access Policy) 14 | - Set-ConditionalAccessPolicy (to update an existing Conditional Access Policy) 15 | - Deploy-ConditionalAccessPolicies (to Deploy a set of policies from a directory) 16 | 17 | To be in control over Conditional Access and to be able to apply efficient and effective version control, it is important that the .Json representation of the policies is readable by the people maintaining them. Since Microsoft Graph can only process GUIDs in .Json format when creating or updating Policies. The issue surfaced that Microsoft Graph and the people maintaining the policies need different versions of the policy files. 18 | To achieve this, the module contains several private function. 19 | 20 | ### The private functions are: 21 | - ConvertFrom-ApplicationDisplayNametoGUID 22 | - ConvertFrom-GroupDisplayNametoGUID 23 | - ConvertFrom-RoleDisplayNametoGUID 24 | - ConvertFrom-UserUserPrinicpleNameToGUID 25 | - ConvertFrom-AgreementDisplayNametoGUID 26 | - ConvertFrom-LocationDisplayNametoGUID 27 | 28 | ----- 29 | - ConvertFrom-ApplicationGUIDtoDisplayName 30 | - ConvertFromGroupGUIDToDisplayname 31 | - ConvertFrom-RoleGUIDtoDisplayName 32 | - ConvertFrom-UserGUIDtoUserPrincipalName 33 | - ConvertFrom-AgreementGUIDToDisplayName 34 | - ConvertFrom-LocationGUIDToDisplayName 35 | 36 | The private functions convert the human-readable DisplayNames (and UserPrincipalNames) that are stated in the policy files to their respective GUIDs in the target AzureAD tenant. The private functions are sub-functions to the public functions, meaning all the conversions happen in the background. 37 | 38 | 39 | ## Getting Started 40 | ### Licensing 41 | To use the functions that actually change the policies in your tenant, you need at least an AzureAD premium P1 license. 42 | For Risk Based Conditional Access Policies you will need an AzureAD premium p2 license. 43 | 44 | ### Start with the demo 45 | 1. Download or clone the repository 46 | 2. Open .\Examples\Script\Demo.ps1 47 | 48 | 3. Go to your Azure tenant and create a new "App Registration" 49 | 4. Fill in a DisplayName of your choice, and choose the "Single tenant" option 50 | 5. Go to the Authentication tab and "Add a Platform". pick the Native Client option: "https://login.microsoftonline.com/common/oauth2/nativeclient" 51 | and choose "Yes" for default Client Type 52 | 6. Under API Permissions add the following permissions and grant Admin Consent: 53 | - User.Read.All 54 | - Application.Read.All 55 | - Group.Read.All 56 | - Policy.Read.All 57 | - Policy.ReadWrite.ConditionalAccess 58 | - RoleManagement.Read.Directory 59 | - Optional permission for automatic group creation: 60 | Group.Create 61 | 7. Go to the "Certificates & secrets" Tab and create a new Secret. Be sure to save the secret somewhere. 62 | 8. In the "Overview" of the App Registration, look for the ClientID and the Directory (tenant)ID. 63 | 9. Go back to .\Examples\Script\Demo.ps1 fill in the variables: $ClientID, $TenantID and $ClientSecret with the information from the steps above. 64 | 10. Run the Import-Module .\ConditionalAccess\ConditionalAccess.psm1 command to import the module 65 | 11. run $AccessToken = Get-AccessToken -ClientId $ClientId -TenantId $TenantId -ClientSecret $ClientSecret to obtain an Access Token 66 | 12. Open the desired Policy .Json file of your choice and adjust the content so that the displayNames in the $File match existing DisplayNames in the Targeted Tenant. The Users in the examples will most likely not exist in your tenant and probably need to be adjusted. 67 | 13. After loading a valid Json in $File you can run the commands you want for the demo 68 | 69 | ### Demo tips 70 | You can use 71 | $ca = Get-ConditionalAccessPolicy -accessToken $AccessToken -DisplayName "CA-01- All Apps - All Admins - Require MFA" 72 | To dissect the content of the created or existing Policy of your choice. e.g. you can run: 73 | $ca.conditions.users.includeRoles 74 | To see that the "All" in the example policy was succesfully translated to a large number of GUIDs of RoleTemplates in the target Tenant. 75 | If you want to see the raw guids from Graph instead of the converted human readable names add -ConvertGUIDs $False. 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | --------------------------------------------------------------------------------