Intune Backups – Part 1: Intune Environment

For the next couple of posts I’m going to cover something close to the hearts of us all, backups!

Picture the scene, someone has accidentally deleted a reasonably complex Intune policy (worse still, it’s a Custom one) and it’s critical at that.

Now, obviously at this point we all fall back to the manual backup taken before making any changes, or the very thorough documentation we have and continually update, but just in case there has been a total outage to wherever these files are stored, wouldn’t it be great to have an automated backup!

Update March 2023, if you want a web-based GUI, check out my new front-end here

Update December 2022 – I have created my own Backup/Restore module which can also be used, see the updated post here

For this I will be using the excellent Intune Backup and Restore module from John Seerdon and PowerShell within an Azure Automation account to make it completely infrastructure free!

First, let’s create the Azure Automation Account:

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

Once that’s complete we want to deploy the required modules to it

In PowerShell Gallery select these two modules and deploy to Azure Automation:

https://www.powershellgallery.com/packages/Microsoft.Graph.Intune/6.1907.1.0

https://www.powershellgallery.com/packages/IntuneBackupAndRestore/3.2.0

Once deployed, we can reference them in our backup script.

We’re also going to need an Azure Storage blob to save the files in:

Add a container and note the name, we’ll need that later

Next up, we need an App Registration we can use to grab the backups

In Entra ID, create a new app registration:

Give it read-only permissions to all of the Device Management options under MS Graph

Update 07/09/2022 – Thanks to Trevor in the comments for spotting this one. You will also need:

DeviceManagementConfiguration.ReadWrite.All

You’ll note it’s still missing permissions so you now need to click the Grant Admin Consent button

Finally create a Secret and save that, the App ID and the Tenant details to use in the script

We also need to give this account access to upload to the Azure Storage Account Container via IAM within the storage account:

Plus Reader to get the Storage Account details

Grab the storage account key, you’ll need this later

Now, back to the Azure Automation Account and click Runbook

Create a new Runbook

Enter the Powershell code below (or grab from here) (update the fields as required for your environment)

<#PSScriptInfo
.VERSION 1.0.1
.GUID 62e6e98b-8580-4c72-b9a4-05c7793a8532
.AUTHOR AndrewTaylor
.DESCRIPTION Automates Backup of Intune Environment
.COMPANYNAME
.COPYRIGHT GPL
.TAGS intune endpoint MEM environment
.LICENSEURI https://github.com/andrew-s-taylor/public/blob/main/LICENSE
.PROJECTURI https://github.com/andrew-s-taylor/public
.ICONURI
.EXTERNALMODULEDEPENDENCIES microsoft.graph.intune, intunebackupandrestore
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
.PRIVATEDATA
#>
<# 

.DESCRIPTION 
Automates Backup of Intune Environment via Intune Backup and Restore module with AAD App Registration, Azure Blob and Azure Automation Account

#> 

import-module intunebackupandrestore

##############################################################################################################################################
##### UPDATE THESE VALUES #################################################################################################################
##############################################################################################################################################
## Your Azure Tenant Name
$tenant = "<YOUR TENANT NAME>"

##Your Azure Tenant ID
$tenantid = "<YOUR TENANT ID>"

##Your App Registration Details
$clientId = "<YOUR CLIENT ID>"
$clientSecret = "<YOUR CLIENT SECRET>"

##Your Storage Account Details
$storagegroup = "<YOUR STORAGE RESOURCE GROUP>"
$storageaccount = "<YOUR STORAGE ACCOUNT>"
$storagecontainer = "<YOUR STORAGE CONTAINER>"
$storagekey = "<YOUR STORAGE KEY>"



##############################################################################################################################################
##### DO NOT EDIT BELOW THIS LINE #############################################################################################################
##############################################################################################################################################
$authority = "https://login.windows.net/$tenant"

## Connect to MS Graph
Update-MSGraphEnvironment -AppId $clientId -Quiet
Update-MSGraphEnvironment -AuthUrl $authority -Quiet
Update-MSGraphEnvironment -SchemaVersion “Beta” -Quiet
Connect-MSGraph -ClientSecret $ClientSecret -Quiet

##Get Date
$date = get-date -format "dd_MM_yyy"

##Create temp folder
$dir = $env:temp + "\IntuneBackup" + $date
$tempFolder = New-Item -Type Directory -Force -Path $dir

##Backup Locally
Start-IntuneBackup `
		-Path $tempFolder
		
##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

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:

As always, comments are most welcome

33 thoughts on “Intune Backups – Part 1: Intune Environment”

  1. Of course I figured this out a few moments after posting. Feel free to remove the comment. When I copied the bulk of the script I accidentally removed the Import-Module command.

    Reply
  2. For some reason I’m getting this message when I try to run the runbook. I’ve imported the required modules to the automation account. Any thoughts? (I understand there is a newer script, but it seems that this simple method of replacing the values in your original script should work without issue.)

    Start-IntuneBackup : The term ‘Start-IntuneBackup’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. At line:66 char:1 + Start-IntuneBackup -Path $tempFolder + ~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (Start-IntuneBackup:String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException

    Reply
  3. Not sure what has changed but when testing in the Azure Automation Account Runbook I get the following error

    The response content cannot be parsed because the Internet Explorer engine is not available, or Internet Explorer’s first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again.
    System.Management.Automation.RuntimeException: You cannot call a method on a null-valued expression.
    at CallSite.Target(Closure , CallSite , Object , String )
    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)
    System.Management.Automation.ParameterBindingValidationException: Cannot bind argument to parameter ‘Path’ because it is an empty string.
    at System.Management.Automation.ParameterBinderBase.ValidateNullOrEmptyArgument(CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, Type argumentType, Object parameterValue, Boolean recurseIntoCollections)
    at System.Management.Automation.ParameterBinderBase.ValidateNullOrEmptyArgument(CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, Type argumentType, Object parameterValue, Boolean recurseIntoCollections)
    at System.Management.Automation.ParameterBinderBase.BindParameter(CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, ParameterBindingFlags flags)
    at System.Management.Automation.CmdletParameterBinderController.BindParameter(CommandParameterInternal argument, MergedCompiledCommandParameter parameter, ParameterBindingFlags flags)
    at System.Management.Automation.CmdletParameterBinderController.BindParameter(UInt32 parameterSets, CommandParameterInternal argument, MergedCompiledCommandParameter parameter, ParameterBindingFlags flags)
    at System.Management.Automation.CmdletParameterBinderController.BindParameters(UInt32 parameterSets, Collection`1 arguments)
    at System.Management.Automation.CmdletParameterBinderController.BindCommandLineParametersNoValidation(Collection`1 arguments)
    at System.Management.Automation.CmdletParameterBinderController.BindCommandLineParameters(Collection`1 arguments)
    at System.Management.Automation.CommandProcessor.BindCommandLineParameters()
    at System.Management.Automation.CommandProcessorBase.DoPrepare(IDictionary psDefaultParameterValues)
    at System.Management.Automation.Internal.PipelineProcessor.Start(Boolean incomingStream)
    at System.Management.Automation.Internal.PipelineProcessor.SynchronousExecuteEnumerate(Object input)
    — End of stack trace from previous location where exception was thrown —
    at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    at System.Management.Automation.Internal.PipelineProcessor.SynchronousExecuteEnumerate(Object input)
    at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boolean ignoreInput, CommandParameterInternal[][] pipeElements, CommandBaseAst[] pipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext funcContext)
    at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)

    Reply
  4. Hello Andrew
    When I start the runbook, I get the following error: Set-AzStorageBlobContent : A command that provides a user prompt failed because the host program or command type does not support user interaction. The host has attempted to request confirmation with the following message: Are you sure you want to overwrite ‘xy’? In line:113 characters:45 +. In addition, no new container is created for me with the current date. What could be the reason for this?
    I copied the powershell script from your github and pasted my values.

    Reply
  5. I had a different approach by creating the config as a json and then uploading them into storage account, this seems better as it can handle all the data created by the backuprestore module, thanks

    Reply
  6. Hi Andrew
    Thanks for this awesome work.

    I have set this up in my environment and had no error message.

    Only thing which i do not see or understand is where can i see the new backup data?
    i have set the backup job to run 4 times in 24 hours. and the jobs execute and complete without any error. But when i check the storage, i only see the very first backup folder and data in there.

    shouldn’t i see multiple parent backup folders with their dates and the datas within each of them?
    Am i looking at it the wrong way?

    Reply
    • Hi,
      Yes, you should see multiple folders in there with each backup in. It looks like the new-dir wqasn’t working properly, I’ve updated the script now which will hopefully work better.
      I’d also have a look at my newer backup script which backs up to GitHub and can grab a lot more settings

      Reply
  7. If it helps anyone: I couldn’t get this working using a Managed Identity in Azure automation – the Microsoft.Graph.Intune module has a bug in the underlying authentication library that returns an error if you try: “Unable to find an entry point named ‘GetPerAdapterInfo’ in DLL ‘iphlpapi.dll’, and the module doesn’t support providing an access token either.

    To use an registered app with client secret, I had to add the following line of code to prevent the PS module from trying to interactively authenticate:
    Update-MSGraphEnvironment -SchemaVersion “Beta” -Quiet

    And an additional API permission was needed on the registered app: DeviceManagementConfiguration.ReadWrite.All

    Reply
  8. I’m getting this error

    Backup Intune Backup and Restore Action IntuneBackupAndRestore – Start Intune Backup Config and Assignments C:\Users\…
    Unable to find an entry point named ‘GetPerAdapterInfo’ in DLL ‘iphlpapi.dll’.
    401 Unauthorized

    Api permissions granted for application

    DeviceManagementApps.Read.All
    DeviceManagementApps.Read.All
    DeviceManagementManagedDevices.Read.All
    DeviceManagementRBAC.Read.All
    DeviceManagementServiceConfig.Read.All

    Could you please tell what I’m doing wrong?

    Reply
  9. Im getting alot of errors and no files are uploaded:

    One of the request inputs is out of range. HTTP Status Code: 400 – HTTP Error Message: One of the request inputs is out of range.
    ErrorCode: OutOfRangeInput
    ErrorMessage: One of the request inputs is out of range.
    RequestId: f14d1152-401e-0069-243f-aeecf4000000
    Time: Fri, 12 Aug 2022 11:37:07 GMT
    One of the request inputs is out of range. HTTP Status Code: 400 – HTTP Error Message: One of the request inputs is out of range.
    ErrorCode: OutOfRangeInput
    ErrorMessage: One of the request inputs is out of range.
    RequestId: e1088207-d01e-0036-5a3f-ae58c8000000

    Looking online, that error is thrown if you violate the naming requirements for the blob/container, usually all lowercase? My container is lowercase and I was able to manually upload a file from a local backup to my device, and that worked ok.

    Reply
  10. I’m walking through this and I’m stumped on granting Device Management permissions to MS Graph. Those options simply don’t show up. The only thing that shows up is MS Graph User.Read permissions but only after I grant admin consent.

    Reply
    • Hi David, are you adding API permissions within Azure AD for the App Registration? The top option should be Microsoft Graph, then Application Permissions and that should give you a long list of permissions to apply

      Reply
      • That did it – I just had to find the API permissions inside the app that was sitting under App registrations. Thanks

        Reply
      • So Just to provide an update, I did get this process running by for my instance I had to take a few extra steps.
        i) I had to have the User Access Administrator role

        ii) DeviceManagementConfiguration.ReadWriteAll permissions had to be granted for MS Graph otherwise I received errors in the Test Pane

        iii) I was also granting User permissions instead of Application permissions for MS Graph. Your screenshot clearly shows Application but I completely missed that during the build

        I didn’t have direct access to the container so I had my permissions configured and I’m just waiting for the changes to bake in so I can verify the upload but it seems to have worked. I appreciate you taking the time to share this.

        Reply

Leave a Comment