Intune – Backup and Restore your environment

Update – 02/08/2023

Added support for GitLab

Update – 07/03/2023

Launched Intunebackup.com to provide a web front end to the script running in an Azure Automation runbook (More info here)

Update – 05/03/2023

Version 5.0 released, changelog:

  • Added extra parameters to trigger backup or restore via ID or Name without GUI at single policy level
  • Added support for webhook
  • Replaced write-host with write-output for use with Azure Automation Runbook
  • Added parameter for filename to skip grid-view on automated restore
  • Bypass script check when running on webhook
  • Github fix to cope with large files

Update – 24/02/2023

Version 4.0 released with a significant performance improvement

Update – 16/02/2023

Version 3.0 now released with added support for Azure DevOps Repo as well as GitHub, can be hard-coded, or via launch parameters

Update – 05/01/2023

Version 2.0 just released adding support for:

  • Policy Sets
  • Enrollment Configuration Policies
  • Device Categories
  • Device Filters
  • Branding Profiles
  • Admin Approvals
  • Intune Terms
  • Custom roles

Introduction

If you are working with virtual machines, you would normally grab a snapshot before making any changes, so why can’t we do the same with Intune/AAD config?

I have previously looked at backing up your Intune environment with an automated big-bang approach (post here), which works well, but:

  • Restoring isn’t as easy as I would like
  • It’s a big-bang restore, everything is restored
  • The names are the same so need to dig around to find which is the new one
  • Versioning isn’t particularly straight forward
  • It uses the (whilst excellent) backup and restore module which relies on the Azure AD module (due for deprecation shortly)

With that in mind, I set about creating my own backup and restore script (yes, script, not module, it’s a single file) which includes:

  • Selective backup, you can pick what policies, Conditional Access, AAD Groups, Proactive Remediations, Script etc. you want to backup (via GUI)
  • A few extra backup options over the previous script
  • Backup to a single file
  • Backup to a Private Github Repo to give you all of the goodness that brings
  • Backups include Commit messages so you know why you made them
  • You can pick which restore point to use
  • You can pick what to restore from the selected restore point
  • Whatever you restore will have “-restore-DATE” at the start of the name so you know what it is and when it was restored (date of restore, not date of backup)!
  • All written using the new Graph SDK
  • Everything you need to set is in parameters to be able to use across multiple customers

So, after many hours of work, here it is. As usual you can grab it from GitHub Here or the PSGallery:

Install-Script -Name intune-backup-restore-withgui

First I’ll run through how the script works and then below how to configure the Github Repo, secret and automate it.

Usage

After configuring your Github repo, you need to run the script with the parameters required (available in get-help too)

intune-backup-restore-withgui.ps1 -type backup -selected some -reponame intunebackups -ownername my-github-account -token my-github-token

Type can be “backup” or “restore” depending on what you are doing

Selected can be “all” to simply grab or restore everything, or anything else will show the gui (it’s an if/else query)

Backup

When running in backup mode, you will see a GUI asking what you want to backup:

You can filter and select multiple via Ctrl/Shift click

Clicking Ok will write them to a single json file and upload it to your specified Github repo including the current date/time to reference when restoring

Restore

When running in Restore mode, you will first see a GUI asking which backup you want to restore from:

After selecting the backup, you will get another GUI showing the content within that backup where you can select what to restore

Click Ok and you’ll see the policies in your tenant with “Restored” at the start of the name

Creating Github Repo and Token

Create a repository, public will also work, but private probably makes more sense for backups

Now we need to create the token, in GitHub navigate to Settings (in the top right menu) – Developer Settings (at the bottom)

I’m using a Personal Access Token, but the API commands are all compatible with a GitHub App if you would prefer

Generate a new classic token and grab the output, it only needs Repo access

Azure AD App Registration

To use in Automated mode, you’ll need to create a secret:

Navigate to Azure AD and click App Registrations, then create a new Application

Give your application a name you will recognise and then select either Single Tenant or Multi-Tenant depending on your requirements, then click Register

Grab the Application (Client ID) from the Overview screen

Now click Authentication

Click Add Platform

You want Mobile or Desktop Application

Tick the 3 boxes and click Configure

Next, select API Permissions and click Add a Permission

Select Microsoft Graph

Select Application Permissions

Add these:

  • DeviceManagementApps.ReadWrite.All
  • DeviceManagementConfiguration.ReadWrite.All
  • DeviceManagementManagedDevices.ReadWrite.All
  • Domain.Read.All
  • Group.ReadWrite.All
  • Organization.Read.All
  • Policy.ReadWrite.ConditionalAccess
  • RoleManagement.ReadWrite.Directory
  • DeviceManagementServiceConfig.ReadWrite.All

You’ll see it’s still not finished, you need to click Grant admin consent

Last step here, click Certificates & secrets

Add a new secret

Add a name you’ll remember and set an expiry (I’m using 24 months because I’m lazy)

Copy the Secret Value somewhere safe, it won’t re-display it and if you lose it you’ll need to create a new one

Automating – Script Amendment

As you can see, normally this runs with a GUI and user authentication, but that won’t work in an automated task.

To automate, change this line to “yes”

############################################################
$automated = "no"
############################################################

Then populate the variables:

$selected = "all"

$reponame = "YOUR_REPONAME_HERE"

$ownername = "YOUR_OWNER_NAME_FOR_REPO"

$token = "YOUR_GITHUB_TOKEN"

$clientid = "YOUR_AAD_REG_ID"

$clientsecret = "YOUR_CLIENT_SECRET"

$tenant = "TENANT_ID"

Automation Deployment

First, let’s create the Azure Automation Account:

Search for Azure Automation in your Azure Portal and create the account:

Now you’ll need to publish some PowerShell modules to the runbook (watch the versions on the Graph modules, it needs the non-preview ones):

For each module, click this button:

https://www.powershellgallery.com/packages/PackageManagement/1.4.8.1

https://www.powershellgallery.com/packages/Microsoft.Graph.Authentication/1.19.0

https://www.powershellgallery.com/packages/Microsoft.Graph.Devices.CorporateManagement/1.19.0

https://www.powershellgallery.com/packages/Microsoft.Graph.Groups/1.19.0

https://www.powershellgallery.com/packages/Microsoft.Graph.DeviceManagement/1.19.0

https://www.powershellgallery.com/packages/Microsoft.Graph.Identity.SignIns/1.19.0

Create a new Runbook

Add the PowerShell code you’ve amended

Click Test Pane to make sure it’s worked

Now we need to publish it once it’s tested ok

Now we can add a schedule to run it weekly

That’s it, your basic weekly/daily/hourly backup is configured and will go direct to the Azure Storage Blob

If you want to get really clever, we can trigger a backup when a setting is changed within Intune using Event Hub. Follow my previous instructions here to create the event hub and link it to Intune and then use this logic app instead:

Select Event Hub as the trigger and use the connection string created before

For the action, find Azure Automation and set it to Create Job, then populate the details:

Then if any changes are made in Intune, you’ll see your Automation Job activate:

73 thoughts on “Intune – Backup and Restore your environment”

  1. I am looking at the code and have found a variable that is unused – $EncodedText
    I wondered if the line:
    $pvalue.value = $unencoded
    was supposed to be:
    $pvalue.value = $EncodedText

    Code as it is right now:

    ##Custom settings only for OMA-URI
    ##Remove settings which break Custom OMA-URI

    if ($null -ne $policy.omaSettings) {
    $policyconvert = $policy.omaSettings
    $policyconvert = $policyconvert | Select-Object -Property * -ExcludeProperty secretReferenceValueId
    foreach ($pvalue in $policyconvert) {
    $unencoded = $pvalue.value
    ##Check if $unencoded is boolean
    if ($unencoded -is [bool] -or $unencoded -is [int] -or $unencoded -is [int32] -or $unencoded -is [int64]) {
    $unencoded = $unencoded.ToString().ToLower()
    }

    $EncodedText = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($unencoded))

    $pvalue.value = $unencoded
    }
    $policy.omaSettings = @($policyconvert)
    }

    Reply
  2. This is just the tool I’ve been looking for! Thanks! Any idea why I’m only seeing AAD Groups when running a backup of everything? (-type backup -selected all)

    Reply
  3. Hi Andrew, if i have a backup and restore set to my tenant but I want to restore the backup to another new tenant. What and where do I make changes to? I have tried to figure this out but i cant seem to find it. Thank you

    Reply
    • its still early morning, found my own answer:

      earlier:
      Hi Randy,
      By default it only outputs to Azure or Github, but if you leave the PS Window open, you could output the raw json:
      $policyjson | out-file c:\temp\mypolicies.json

      Reply

      Reply
      • tried this, but all variables were empty.

        all i want to do is a backup to my computer.
        Do i need to create the azure automation runbook in order to backup ?
        or just an app registration / Global administrator login will suffice ?

        sidenote, i made this to export all variables to json, might be usefull for people. (chatggpt generated)

        # Set the threshold time to just before the script was run
        $thresholdTime = [datetime]::ParseExact(“2024-04-04 11:45”, “yyyy-MM-dd HH:mm”, $null)

        # Get all user-defined variables
        $variables = Get-Variable | Where-Object { -not $_.Name.StartsWith(“_”) -and -not $_.Name.Contains(“::”) }

        foreach ($variable in $variables) {
        # Convert each variable to JSON, handling types as necessary
        $contentToConvert = $variable.Value

        if ($contentToConvert -is [System.Collections.IDictionary]) {
        # Convert dictionaries to hashtable
        $contentToConvert = @{}
        foreach ($key in $variable.Value.Keys) {
        $contentToConvert[$key] = $variable.Value[$key]
        }
        } elseif ($contentToConvert -is [System.Collections.IEnumerable] -and -not ($contentToConvert -is [string])) {
        # Convert other enumerable types to arrays
        $contentToConvert = @($variable.Value)
        } else {
        # Wrap non-enumerable, non-dictionary types as custom objects
        $contentToConvert = [PSCustomObject]@{Value = $contentToConvert}
        }

        try {
        # Convert the content to JSON
        $jsonContent = $contentToConvert | ConvertTo-Json -Depth 5
        } catch {
        Write-Warning “Could not convert variable $($variable.Name) to JSON: $_”
        continue
        }

        # Export to JSON file, naming the file after the variable
        try {
        $fileName = “.\” + $variable.Name + “.json”
        $jsonContent | Out-File $fileName
        } catch {
        Write-Warning “Could not write variable $($variable.Name) to file: $_”
        }
        }

        Reply
  4. Hello Andrew,

    PS5 is the winner…
    You can’t restore backups made by PS7 but PS5 / PS5 is working just fine…

    thanks

    Reply
  5. I am not able to figure this out, i have followed all the steps in your article, but i keep getting:

    Uploading to Github
    Invoke-RestMethod : {“message”:”Not
    Found”,”documentation_url”:”https://docs.github.com/rest/repos/contents#create-or-update-file-contents”}
    At C:\Program Files\WindowsPowerShell\Scripts\intune-backup-restore-withgui.ps1:5766 char:2
    + (Invoke-RestMethod -Uri $uri -Method put -Headers @{‘Authorization’=’ …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
    eption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

    intune-backup-restore-withgui.ps1 -type backup -select all -reponame -ownername -token

    I am an writer etc on the affected GItHub Repo and my account is using the correct token.

    Any idea what i’m doing wrong?

    Reply
  6. Hello Andrew,

    thank you for your work…

    I have just some issues with backup / restore…
    First, just some error during the backup:

    Line |
    475 | Select-MgProfile -Name Beta
    | ~~~~~~~~~~~~~~~~
    | The term ‘Select-MgProfile’ is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try
    | again.

    Despite that, backup proceeded without further errors. But during the test restore attempt of the Intune configuration profile from Github Repository:

    Invoke-RestMethod: C:\Program Files\WindowsPowerShell\Scripts\intune-backup-restore-withgui.ps1:5868:31
    Line |
    5868 | … download = (Invoke-RestMethod -Uri $commitfilename2 -Method Get -Head …
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | The format of value ‘Accept: application/json’ is invalid.
    Invoke-RestMethod: C:\Program Files\WindowsPowerShell\Scripts\intune-backup-restore-withgui.ps1:5869:46
    Line |
    5869 | … ecodedbackup = (Invoke-RestMethod -Uri $decodedbackupdownload -Method …
    | ~~~~~~~~~~~~~~~~~~~~~~
    | Cannot validate argument on parameter ‘Uri’. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
    0
    InvalidOperation: C:\Program Files\WindowsPowerShell\Scripts\intune-backup-restore-withgui.ps1:5988:5
    Line |
    5988 | $value1 = ($profilelist3)[2]
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | Cannot index into a null array.
    InvalidOperation: C:\Program Files\WindowsPowerShell\Scripts\intune-backup-restore-withgui.ps1:5989:5
    Line |
    5989 | $prid = ($profilelist3)[3]
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~
    | Cannot index into a null array.

    any idea what should I do to resolve it?

    Reply
    • Hi,
      The first error is usually if you have both V1 and V2 modules installed, it works, but uses V1 commands on V2.

      Can you try changing the GitHub header to:
      Accept: application/vnd.github+json

      If that works, I’ll update the script

      Reply
      • Invoke-RestMethod: C:\Install\X\xintune-backup-restore-withgui.ps1:5868
        Line |
        5868 | … download = (Invoke-RestMethod -Uri $commitfilename2 -Method Get -Head …
        | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        | The format of value ‘Accept: application/vnd.github+json’ is invalid.
        Invoke-RestMethod: C:\Install\X\xintune-backup-restore-withgui.ps1:5869
        Line |
        5869 | … ecodedbackup = (Invoke-RestMethod -Uri $decodedbackupdownload -Method …
        | ~~~~~~~~~~~~~~~~~~~~~~
        | Cannot validate argument on parameter ‘Uri’. The argument is null or empty. Provide an argument that is not null
        | or empty, and then try the command again.
        0
        InvalidOperation: C:\Install\X\xintune-backup-restore-withgui.ps1:5988
        Line |
        5988 | $value1 = ($profilelist3)[2]
        | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        | Cannot index into a null array.
        InvalidOperation: C:\Install\X\xintune-backup-restore-withgui.ps1:5989
        Line |
        5989 | $prid = ($profilelist3)[3]
        | ~~~~~~~~~~~~~~~~~~~~~~~~~~
        | Cannot index into a null array.

        Reply
          • Personal access tokens (classic) created ad hoc just for this project… I followed your “how to” But since it’s working for you I’ am gonna do some digging at my end… Just not sure if I have a problem at the github or azure end…

  7. Ive set this up and im running the TEST in the runbook. I get the following output during the test but after a period of time it just seems to stop doing anything. I refresh the job streams and no new lines come up and the test never seems to complete.

    I dont have many policies but so far this “TEST” has been running for 2 hours and nothing has moved past this point. What am i doing wrong? No errors seem to display either.

    I can run a manual backup using the GUI fine but the automation doesn’t seem to want to work.

    It’s an enrollment configuration
    85fd1e7e-0e6d-41bd-887f-06f69ab36314_DefaultWindowsHelloForBusiness
    It’s an AutoPilot ESP
    It’s a WHfB Policy
    It’s an enrollment configuration
    91bbf654-048b-4f9d-a9ff-12b515263118
    It’s a device filter
    36d53cb6-5509-421d-8517-a4f7c1d54ef3
    It’s a device filter
    ead8b24e-ac7a-488a-baec-cfed41577c95
    It’s a device filter
    5bcd81e0-bd7f-494b-bbc6-93bc4c81c1f2
    It’s a device filter
    f199cbe2-f45d-4efa-8511-2c9330620063
    It’s a device filter
    ee05f163-1e98-4029-a13e-40a440f7abbf

    Reply
      • Nothing in the output no, only what I’ve pasted above are the last few lines but other than that no errors.

        When you say app reg permissions your referring to these?

        DeviceManagementApps.ReadWrite.All
        DeviceManagementConfiguration.ReadWrite.All
        DeviceManagementManagedDevices.ReadWrite.All
        Domain.Read.All
        Group.ReadWrite.All
        Organization.Read.All
        Policy.ReadWrite.ConditionalAccess
        RoleManagement.ReadWrite.Directory
        DeviceManagementServiceConfig.ReadWrite.All

        I have all of these set as per your guide.

        Reply
          • Im not sure where I would find that? Im brand new to Runbooks and Azure automation. Literally only set it up so I could automate my Intune backups.

            Im using github for the repository and have put those values into the script in the runbook.

            It must be connecting to my tenant to be able to pick this up and get the policys IDs so it must be connecting. Why it just randomly stops in the middle though is where im stuck

            It’s an enrollment configuration
            85fd1e7e-0e6d-41bd-887f-06f69ab36314_DefaultWindowsHelloForBusiness
            It’s an AutoPilot ESP
            It’s a WHfB Policy
            It’s an enrollment configuration
            91bbf654-048b-4f9d-a9ff-12b515263118
            It’s a device filter
            36d53cb6-5509-421d-8517-a4f7c1d54ef3
            It’s a device filter
            ead8b24e-ac7a-488a-baec-cfed41577c95
            It’s a device filter
            5bcd81e0-bd7f-494b-bbc6-93bc4c81c1f2
            It’s a device filter
            f199cbe2-f45d-4efa-8511-2c9330620063
            It’s a device filter
            ee05f163-1e98-4029-a13e-40a440f7abbf

  8. Hi, Thanks for this. After setting up everything and wanted to test restoring; using this: intune-backup-restore-withgui.ps1 -type restore -selected all -reponame *********-ownername ***** -token ghp_******************************

    it only gives me the option to select the backup but doesnt open the backup to select the specific item i want to restore. I see a notification ‘do you want to restore assignments?’ and attempts to restore all

    please is the script broken?

    Reply
  9. Hello, the script is working as a charm!

    But when we run it in a runbook it gets this error:
    Thread failed to start. (Thread failed to start. (Exception of type ‘System.OutOfMemoryException’ was thrown.))

    There is a memory limit on the azure sandbox of 400Mb so I guess the workerthread exceeds this. Is there any way to divide the automated backup into pieces or any other solution you can think of?

    It is only one customer that this happens to and they have alot of configurations.
    Thanks

    Reply
  10. Getting this error when running in Automation Account:

    fd6ac55e-d57c-47b9-8803-f9c3eb3789cb
    It’s a policy
    System.Management.Automation.MethodException: Cannot convert argument “chars”, with value: “True”, for “GetBytes” to type “System.Char[]”: “Cannot convert value “True” to type “System.Char[]”. Error: “Invalid cast from ‘System.Boolean’ to ‘System.Char[]’.”” —> System.Management.Automation.PSInvalidCastException: Cannot convert value “True” to type “System.Char[]”. Error: “Invalid cast from ‘System.Boolean’ to ‘System.Char[]’.” —> System.InvalidCastException: Invalid cast from ‘System.Boolean’ to ‘System.Char[]’.
    at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
    at System.Management.Automation.LanguagePrimitives.ConvertIConvertible(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable)
    — End of inner exception stack trace —
    at System.Management.Automation.LanguagePrimitives.ConvertIConvertible(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable)
    at System.Management.Automation.LanguagePrimitives.ConvertCheckingForCustomConverter.Convert(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable)
    at CallSite.Target(Closure , CallSite , Encoding , Object )
    — End of inner exception stack trace —
    at System.Management.Automation.ExceptionHandlingOps.ConvertToArgumentConversionException(Exception exception, String parameterName, Object argument, String method, Type toType)
    at CallSite.Target(Closure , CallSite , Encoding , Object )
    at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
    at System.Management.Automation.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    4b2d8e79-e50a-4a21-95ea-8cd7acd91179
    It’s a policy
    c3c07401-dc4e-4a10-b6ee-34ba2f8c2af3
    It’s a policy

    Then it continues to cycle through the policies but fails in the end:

    An error occurred stopping transcription: The host is not currently transcribing.

    Reply
    • The transcript error will happen in automation. The other error is probably pagination and shouldn’t cause any issues.
      Have a look for any errors when exporting the JSON, it’s normally a permissions issue at the Git level

      Reply
  11. Hello Andrew,

    We have created the backup account successfully using above details, but we are unable to identify where its creating its backup. As i am assuming it should be under Storage browser>Blob container>the name of your backup. But when i click it after running the runbook successfully getting no backup job created there. More details, we donot have anything currently in our intune tenant.

    Reply
    • Hi, it will backup to whichever Git repo you tell it to use, either with a parameter or embedded in the script.
      You could also use my intunebackup.com service if you want a better GUI

      Reply
  12. Hi Andrew,

    What command would I use if I want to save the backup locally? Is that an option with this script, or are GitHub or Azure the only options for saving?

    Thanks in advance,
    Randy

    Reply
  13. In this case, I used Azure DevOps. However, I used to manually execute a pull request after every commit. But when I stopped doing that and instead selected the commit as a backup directly, the error no longer occurred.

    However, I’m getting the following error when backing up all of my Windows Autopatch configuration profiles (settings catalog):

    Invoke-MgGraphRequest : Cannot convert ‘System.Object[]’ to the type ‘System.Uri’ required by parameter ‘Uri’.
    Specified method is not supported.
    At C:\tmp\intune-backup-restore-withgui.ps1:1965 char:57
    + … $nextsettings = (Invoke-MgGraphRequest -Uri $policynextlink -Method …
    + ~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidArgument: (:) [Invoke-MgGraphRequest], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,Microsoft.Graph.PowerShell.Authentication.Cmdlets.InvokeMgGraphReq
    uest

    It is the identical error for all Windows autopatch profiles

    Reply
  14. Hi Andrew,
    Thank you for your great work.
    I always get the following error when trying to restore. Regardless of whether Win365 user settings, normal config profiles, etc.

    ConvertFrom-Json : Invalid JSON primitive: .
    At C:\tmp\intune-backup-restore-withgui.ps1:2824 char:34
    + $profilelist2 = $decodedbackup | ConvertFrom-Json
    + ~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [ConvertFrom-Json], ArgumentException
    + FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand

    Cannot index into a null array.
    At C:\tmp\intune-backup-restore-withgui.ps1:2841 char:5
    + $value1 = ($profilelist3)[2]
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Do you have any idea what it could be?

    Thanks very much!

    Reply
  15. Looks a lot better now yes. Only a few errors, and now it really qucikly displays the list of config to select for backup.

    PS>TerminatingError(Invoke-MgGraphRequest): “GET https://graph.microsoft.com/beta/identity/conditionalAccess/policies
    HTTP/1.1 403 Forbidden

    PS>TerminatingError(Invoke-MgGraphRequest): “GET https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/provisioningPolicies

    PS>TerminatingError(Invoke-MgGraphRequest): “GET https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/userSettings

    I’m pretty sure I added the required rights, and I checked using graph explorer, the rights and queries, then compared with api permissions.

    If I just select canced after the list of config is displayed, and then cancel on the backup path it does not seem to by happy about that:
    At C:\Temp\intune-backup-restore-withgui-v211.ps1:2456 char:1
    + $profilesencoded =[Convert]::ToBase64String([Text.Encoding]::UTF8.Get …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentNullException
    Exception calling “GetBytes” with “1” argument(s): “Array cannot be null.
    Parameter name: chars”
    At C:\Temp\intune-backup-restore-withgui-v211.ps1:2456 char:1
    + $profilesencoded =[Convert]::ToBase64String([Text.Encoding]::UTF8.Get …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentNullException

    Reply
  16. Hello, could you give the script a try with the new modules 1.21.0 released a few days ago, I’m getting various errors all over the place, however it does succeed in backing up the configuration to json on github, and the content of the json seems to be as expected for the configuration I exported. Thanks for all the effort!

    Reply
  17. Hi Andrew,

    Has your script changed or updated?
    the script lines you mentioned in these comments don’t match what you say.

    I’m a noob to scripting and i’m super confused with reading this article and trying to figure out your script.

    To keep it simple, i have the following questions to which i’d like to request clarity:
    1. Which part in the script do I need to modify exactly to automate the backups to get stored in Azure Storage? Can you please point out the specific lines in the script and what the changes would be?

    2. Is there an existing script of yours that has only Azure storage as the backup & restore platform and not Github repo.?

    I thank you in advance for your help & guidance.

    Reply
    • Hi,
      Yes, the script is regularly updated with new features and bug fixes, in this case fixing some issues with larger settings catalog policies. The lines you need to amend haven’t been changed, but the line numbers will just have moved further down.
      The script grabs all policies into a variable which it then adds to a file and uploads to GitHub, this is the part you need to change so it uploads to Azure.

      With the restore, you need to remove the GitHub parts and grab your file from Azure storage into a variable called $decodedbackup

      I don’t have a version with Azure storage, the GitHub API gives me better control over versioning when using the GUI

      If you’re starting out with PowerShell, this book comes well recommended:
      “Learn PowerShell in a Month of Lunches”

      Reply
  18. Hi Andrew,

    Wouldn’t more in the script need to be changed/modified?

    For example:
    -Line 102-120 (Variables) – as this isn’t pointing to Azure
    -Line 212 to 215 (Connect to Graph) – addition of the updated permissions
    -Line 2309 – this points to Github

    -Line 2312 – should this be replaced with the following for the connection & upload to Azure work correctly:

    ##Connect to AZURE
    $azurePassword = ConvertTo-SecureString $clientSecret -AsPlainText -Force
    $psCred = New-Object System.Management.Automation.PSCredential($clientID , $azurePassword)
    Connect-AzAccount -Credential $psCred -TenantId $tenantid -ServicePrincipal

    ##Convert Storage Account to lowercase (just in case)

    $storageaccount = $storageaccount.ToLower()

    ##Upload to Azure Blob
    $files = “$env:TEMP\IntuneBackup$date”
    $context = New-AzStorageContext -StorageAccountName $storageaccount -StorageAccountKey $storagekey
    Get-ChildItem -Path $files -File -Recurse | Set-AzStorageBlobContent -Container $storagecontainer -Context $context

    And about your comment to ‘add a line to read the azure file into into a variable called $decodedbackup’ – I’m not sure i follow. Could you please help & paste here what you mean ?

    Reply
    • Lines 102-120 just won’t get used because you aren’t calling the variables, same with line 2309
      The permissions depends on how you are connecting to Azure, if you are using a shared key you just need headers and a webrequest

      That azure connection will work, but that’s uploading the IntuneBackup file which won’t currently exist so you’ll need to create that with the JSON content first.

      When performing a restore, you need to read the content into a variable called $decodedbackup so the rest of the script can use it

      Reply
  19. Hi Andrew,
    I had earlier setup the backup as per one of your previous article https://andrewstaylor.com/2022/03/28/intune-backups-part-1-intune-environment/

    And as you commented and pointed out this one, i’m trying to setup this one.

    While setting up the App registration, i was able to find and grant all except the following 2 permissions:

    Policy.ReadWrite.All
    RoleAssignmentSchedule.ReadWrite.All

    i looked through but do not see the above 2 anymore. Have they been renamed in Azure?

    Also, this article mentions how to setup the backup to be saved in Github repo. Is there also a way to backup & restore in Azure storage?
    If yes, then from which parts in the script am i supposed to enter/used in the Runbook?

    Reply
    • Hi,
      Yes, looks like it’s had an update, these two should replace it I think:
      Policy.ReadWrite.ConditionalAccess
      RoleManagement.ReadWrite.Directory

      Line 2312 does the copy to GitHub so you could change that to store in an Azure blob

      For the restore, comment out lines 2335 to 2356 and add a line to read the azure file into a variable called $decodedbackup

      Reply
  20. Here is the command I used:

    intune-backup-restore-withgui.ps1 -type backup -selected some -reponame AppManifests -ownername [GITHUB USERNAME] -token [CLASSIC TOKEN]

    Reply
  21. I get this error when I try to run a backup:

    Line |
    2293 | (Invoke-RestMethod -Uri $uri -Method put -Headers @{‘Authorization’=’ …
    | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    | The format of value ‘Accept: application/json’ is invalid.

    Reply

Leave a Comment