Why backup Compliance Policies?

Why would you need to back up something that runs in stored and hosted on Azure? Well there are numerous answers to this question really.

  • If you make a change and you break something you can look back and analyse what it was
  • You can make copies of the policy easily rather than having two windows side by side
  • In case one is deleted… (Let’s hope this is never the case)
  • To review the maturity of your policies (Lets say you started from ground zero and now have over 100 policy settings… Might be nice to review what you’ve done)

We also live in a world of change management and service improvement  so there is always a need to make changes to policies and configurations. If you have or are moving from traditional group policy you will know that you can run an HTML report or a backup before you go ahead and make any changes.

Well when using Intune there was no way to export or backup your profiles or policies from the console… I have seen people taking screenshots of the pages as a backup of the policies which is far from an ideal scenario.

What if I told you there is a way you can back-up your configuration policies using the Microsoft Graph API?? Well it’s possible and it’s easier than you think.

This is part of a series of posts about backing up and importing policies and profiles, so if you feel like you’ve read this part before then you probably have.

Back when I wrote my first post about these (HERE) the script just backed up the policies/profiles, however over time they have grown into scripts that you can also use to re-import these policies/profile.

This one is the Second in the series, where we will focus on Compliance PoliciesEach one has brought its own challenges which are hopefully mitigated within the script, but if not you can always get in touch and let me know.

The Script

You will notice that most of this (the authentication part and most of the param block at least) are the same as my other script… But if its not broke why fix it? (Those famous last words!!!). Although this script does have an alternative run method, if you run it directly without the ClientID, ClientSecret and TenantID parameters it will install the Azure AD Powershell module and use a custom Function (Connect-AzAD_Token) to enable users to interact with a login Window if they do not wish to use Azure AD App Registrations with client secrets.

This script can be run from anywhere, as a user (If the using the command line parameters or if the AzureAD Module is installed already), as an Administrator or even as System. You could put this into an Automation Engine to do backups on a schedule if that is your desire but this would need to be done with an Azure App Registration.

param(
    [Parameter(DontShow = $true)]
    [string]
    $MsGraphVersion = "beta",
    [Parameter(DontShow = $true)]
    [string]
    $MsGraphHost = "graph.microsoft.com",
    #The AzureAD ClientID (Application ID) of your registered AzureAD App
    [string]
    $ClientID,
    #The Client Secret for your AzureAD App
    [string]
    $ClientSecret,
    #Your Azure Tenent ID
    [string]
    $TenantId,
    [Parameter()]
    [string]
    $OutputFolder = "./CompliancePolicyBackup",
    [switch]
    $Import,
    [string]
    $ImportJSON

)#

FUNCTION Connect-AzAD_Token {
    Write-Host -ForegroundColor Cyan "Checking for AzureAD module..."
    $AADMod = Get-Module -Name "AzureAD" -ListAvailable

    if (!($AADMod)) {
        Write-Host -ForegroundColor Yellow "AzureAD PowerShell module not found, looking for AzureADPreview"
        $AADModPrev = Get-Module -Name "AzureADPreview" -ListAvailable
        #Check to see if the AzureAD Preview Module is insalled, If so se that as the AAD Module Else Insall the AzureAD Module
        IF ($AADModPrev) {
            $AADMod = Get-Module -Name "AzureADPreview" -ListAvailable
        } else {
            try {
                Write-Host -ForegroundColor Yello "AzureAD Preview is not installed..."
                Write-Host -ForegroundColor Cyan "Attempting to Install the AzureAD Powershell module..."
                Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ErrorAction Stop | Out-Null
                Install-Module AzureAD -Force -ErrorAction Stop
            }
            catch {
                Write-Host -ForegroundColor Red "Failed to install the AzureAD PowerShell Module `n $($Error[0])"
                break 
            }
           
        }

    } else {
        Write-Host -ForegroundColor Green "AzureAD Powershell Module Found"
    }

    $AADMod = ($AADMod | Select-Object -Unique | Sort-Object)[-1]
    
    $ADAL = Join-Path $AADMod.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
    $ADALForms = Join-Path $AADMod.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
    [System.Reflection.Assembly]::LoadFrom($ADAL) | Out-Null
    [System.Reflection.Assembly]::LoadFrom($ADALForms) | Out-Null

    $UserInfo = Connect-AzureAD

    # Microsoft Intune PowerShell Enterprise Application ID 
    $MIPEAClientID = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
    # The redirectURI
    $RedirectURI = "urn:ietf:wg:oauth:2.0:oob"
    #The Authority to connect with (YOur Tenant)
    Write-Host -Foregroundcolor Cyan "Connected to Tenant: $($UserInfo.TenantID)"
    $Auth = "https://login.microsoftonline.com/$($UserInfo.TenantID)"

    try {
        $AuthContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $Auth
        
        # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
        # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession
        $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
        $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($UserInfo.Account, "OptionalDisplayableId")
        $authResult = $AuthContext.AcquireTokenAsync(("https://" + $MSGraphHost),$MIPEAClientID,$RedirectURI,$platformParameters,$userId).Result
        # If the accesstoken is valid then create the authentication header
        if($authResult.AccessToken){
            # Creating header for Authorization token
            $AADAccessToken = $authResult.AccessToken
            return $AADAccessToken
        } else {
            Write-Host -ForegroundColor Red "Authorization Access Token is null, please re-run authentication..."
            break
        }
    }
    catch {
        Write-Host -ForegroundColor Red $_.Exception.Message
        Write-Host -ForegroundColor Red $_.Exception.ItemName
        break
    }
}

# Web page used to help with getting the access token 
#https://morgantechspace.com/2019/08/get-graph-api-access-token-using-client-id-and-client-secret.html 


if (($ClientID) -and ($ClientSecret) -and ($TenantId) ) {
    #Create the body of the Authentication of the request for the OAuth Token
    $Body = @{client_id=$ClientID;client_secret=$ClientSecret;grant_type="client_credentials";scope="https://$MSGraphHost/.default";}
    #Get the OAuth Token 
    $OAuthReq = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -Body $Body
    #Set your access token as a variable
    $global:AccessToken = $OAuthReq.access_token
} else {
    $global:AccessToken = Connect-AzAD_Token
}

IF (!($Import)) {
    $FormattedOutputFolder = "$OutputFolder$(Get-Date -Format yyyyMMdd_HH-mm-ss)"

    IF (!(Test-Path $FormattedOutputFolder)){
        try {
            mkdir $FormattedOutputFolder -ErrorAction Stop | Out-Null
        }
        catch {
            Write-Host -ForegroundColor Red "Failed to create $FormattedOutputFolder"
            $Error[0]
            break
        }
        
    }

    Invoke-RestMethod -Method GET -Uri "https://$MSGraphHost/$MsGraphVersion/deviceManagement/deviceCompliancePolicies" -Headers @{Authorization = "Bearer $AccessToken"} | Select-Object -ExpandProperty "Value" | %{
        $_ | ConvertTo-Json | Out-File "$FormattedOutputFolder$($_.displayname).json"
    } 
}elseif ($Import) {
    IF ($ImportJSON){
        #$JSON = GET-Content $ImportJSON
        $JSON = Get-Content $ImportJSON | ConvertFrom-Json | Select-Object -Property * -ExcludeProperty Version,LastModifiedTime,CreatedDateTime,id | ConvertTo-Json
        $SAFRule = '"scheduledActionsForRule":  [
        {
            "ruleName": "PasswordRequired",
            "scheduledActionConfigurations":    [
                {
                    "actionType":   "block",
                    "gracePeriodHours": 0,
                    "notificationTemplateId":   "",
                    "notificationMessageCCList":    [
                        ]
                    }
                ]
            }
        ]'   
        
        $JSON = $Json.trimend("}") + "," + "`r`n" + $SAFRule + "`r`n" + "}"
        Invoke-RestMethod -Method POST -Uri "https://$MSGraphHost/$MsGraphVersion/deviceManagement/deviceCompliancePolicies" -Headers @{Authorization = "Bearer $AccessToken"} -Body $JSON -ContentType "application/json"    
    } else {
        Write-Host -ForegroundColor RED "You must specify an a JSON file using the -ImportJSON parameter"
    }
}

The Pre-Reqs

With an Azure AD App Registration

To make the script work you will need an Azure App Registration with the following permissions for the Microsoft Graph API;

For backing up the Compliance Policies you will need the DeviceManagementConfiguration.Read.All permission (NOTE: This will need to be Application permissions if you are using the App Registration).

If you wish to import Compliance Policies you will need the DeviceManagementConfiguration.ReadWrite.All permission.

GRAPH API DOCUMENTATION

You will also need the Tenant ID and the account that the script will be running as will need permission to the Output folder.

If your not sure how to create an Azure AD App Registration head over to one of my other posts by clicking HERE, Don’t forget to store your Client ID and Secret securely and also have it to hand for the rest of the post :D.

Executing the Script

With an Azure AD App Registration

You can run this script directly from a PowerShell console, using Task Scheduler or using a 3rd party automation product that supports Powershell.

The main thing we will go through here is just the parameters and then putting them all together from the command line, it’s really that simple.

For Backup Only

  • Client ID: This is the Client ID for your Azure AD App
  • ClientSecret: The Client Secret for the Azure AD App
  • TenantID: Your Azure Tenant ID
  • OutputFolder: Your desired Output folder

./Backup_Import_CompliancePolicies.ps1 -ClientID “” -ClientSecret “” -TenantID “” -OutputFolder “./YourServerBackups/CompliancePolicies”

For Importing Policies

  • Client ID: This is the Client ID for your Azure AD App
  • ClientSecret: The Client Secret for the Azure AD App
  • TenantID: Your Azure Tenant ID
  • Import: This is a switch parameter which states if your intention is to import or not
  • ImportJSON: the path to your JSON file.

You will finally end up with something like this;

./Backup_Import_CompliancePolicies.ps1 -ClientID “” -ClientSecret “” -TenantID “” -Import -ImportJSON “./YourServerBackups/CompliancePolicies/ImportMe.JSON”

Direct Execution

If you launch the script without the Client ID, Secret and Tenant ID you will be prompted with a Microsoft Logon Window similar to the below.

Once you login the script will continue to run and then output the configuration files in the same way it would using the App Registration.
You will need an account with permissions to be able to read (for backups only) or Read and Write the Compliance Policies. However the likelihood is that if you are looking at this guide you are probably an Intune Service Administrator or Global Administrator on your Tenant.

When you run it directly without any switches the script will prompt you to log in and it would only perform a backup of your profiles and output the configurations to the the folder you are executing it from.

If you add the -OutputFolder parameter you can change the destination of the base output folder. However if you are wishing to use the script to Import policies you can add the -Import and -ImportJSON parameters, If you specify the -Import  parameter you must also specify the -ImportJSON parameter with a path to the JSON file (e.g. C:/ImportMe.json) otherwise the script will display a message that you did not specify the -ImportJSON Parameter.

You will notice that when you run the script, if the folder does not exist it will create it. It also put its into a dated folder in the yyyyMMdd_HH-mm-ss format leaving you with something like 20200901_16_05_36

Summary

This can also be useful if you are wanting to make a copy of your policies to assign to a test machine. All you will need to do is backup your current policies and amend the JSON file, If you find the displayName field in the JSON file and amend it and save the file you will be able to re-import this the same settings. All you then need to do is assign it.

I have tested this myself at the time of writing the post but if you come across any information you think may be wrong then please leave a comment or e-mail me on david@euc365.com.

I hope this is useful for your needs.