# How to easily backup your Intune environment using IntuneCD and Azure DevOps Pipeline

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">27.3.2024 modified to use Workload Federation Identity instead of a separate Azure Service Principal</div>
</div>

I was quite surprised when I found out how easy is to regularly back up your Intune environment configuration using the great [IntuneCD](https://github.com/almenscorner/IntuneCD) tool and [Azure DevOps](https://azure.microsoft.com/en-us/products/devops) service.

Today I will show you how I took the [existing solution](https://stealthpuppy.com/automate-intune-documentation-azure/) and improved it so that even the author of the Intune configuration change is included in the backup.

The result?

You can easily browse through your Intune environment history saved in your GIT repository and see **who** made **what** change and **when** it happened 🤯

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697731381475/85c92aba-0bcf-4f16-9d38-6fe9d12e3e3b.png align="center")

---

# Main benefits of this solution

* it is **free of charge**
    
* **all your Intune configuration will be regularly backed up** to your private Azure DevOps GIT repository
    
* **visibility to Intune changes** made during the time **including the author of such change**
    
* ability to see **how the Intune was configured** at a specified point in time
    
* runs in Azure DevOps Pipeline a.k.a. purely code-driven & cloud-driven (**no on-premises requirements whatsoever**)
    

---

# Requirements

* Azure Global Administrator permissions so you can
    
    * create an Azure DevOps project and Workload Federating identity
        
    * grant required Graph Api permissions
        

---

# Let's do this!

This solution needs:

* **Azure DevOps**
    
    * a.k.a. GIT repository
        
* Azure DevOps **Pipeline**
    
    * to run the code that will do all the magic
        
* **Workload Federating identity**
    
    * a.k.a. Azure Service Principal bind to the Azure DevOps project that will be used to authenticate against Graph Api
        

---

## Azure DevOps repository

### Create a private Azure DevOps repository

Go to [https://dev.azure.com/](https://dev.azure.com/) and create a PRIVATE new GIT repository (project).

`Intune Backup` in my case.

### Assign contribute permission

Go to `Project settings` &gt; `Repositories` &gt; `Security` and grant **Contribute 'ALLOW'** permission to `Build Service` account. Otherwise, pipeline jobs will fail when a commit is made.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695732312964/4f2d3eac-37ce-484c-b8c2-32a1050571bb.png align="center")

---

## **Workload Federating identity**

### Create **Workload Federating identity**

Instead of using Azure Service Principal (SP) to authenticate to Graph Api, we will use Workload Federating identity. The main benefit is that you don't have to worry about passing SP secret to your pipeline, renewing it, etc.

How to create a Workload Federating identity is outside of the scope of this article and is described in my [other post](https://doitpsway.com/how-to-authenticate-to-microsoft-graph-from-azure-devops-pipeline-using-workload-identity-federation).

It is just a few steps, the only important thing is to remember, what name you have used to create `Service connection name` during the setup because we will use it in our pipeline later.

In my case it is **intune\_backup\_connection**.

### Grant required permissions

Grant the following Graph API **application** permission to the created Workload Federating Identity:

* `DeviceManagementApps.Read.All`
    
* `DeviceManagementConfiguration.ReadWrite.All` (Read**Write** [because of this "feature"](https://github.com/almenscorner/IntuneCD/discussions/141))
    
    * **TIP**: if you are afraid of giving ReadWrite permission (I am 😀), you can grant just `DeviceManagementConfiguration.Read.All`, but don't forget to add the parameter `--ignore-omasettings` when calling `IntuneCD-startbackup` command in the pipeline later!
        
* `DeviceManagementManagedDevices.Read.All`
    
* `DeviceManagementServiceConfig.Read.All`
    
* `Group.Read.All`
    
* `Policy.Read.All`
    
* `Policy.Read.ConditionalAccess`
    
* `DeviceManagementRBAC.Read.All` (since [2.1.0](https://github.com/almenscorner/IntuneCD/releases/tag/v2.1.0) version)
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">To grant permissions programmatically, you can use <code>Grant-AzureServicePrincipalPermission</code> (part of the <a target="_blank" rel="noopener noreferrer nofollow" href="https://www.powershellgallery.com/packages/AzureApplicationStuff" style="pointer-events: none"><code>AzureApplicationStuff</code></a> module)</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711552777641/6520ca65-d87d-492d-9cc7-b7fb2eba4fb5.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Don't forget to <code>Grant admin consent</code> for all those permissions too!</div>
</div>

---

## Azure DevOps repository pipeline

The pipeline is where all the magic happens. It is in fact script that defines what should happen.

### Create new Pipeline

* To be able to create a pipeline, your repository needs to be initiated first.
    
    * Go to `Repos` &gt; `Files` &gt; choose `Initialize`
        

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697731212426/978853aa-9d52-4faf-8f6e-da4deb798188.png align="center")

* Now go to `Pipelines` and create the pipeline using `Create Pipeline` button
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697187654577/1d13a7ec-656c-42ae-9db9-4bb03cf26848.png align="center")

* Select `Azure Repos Git`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695724142358/e25981e3-d054-4099-a5eb-c2a14157a237.png align="center")

* Select `Starter pipeline`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695724256103/2861e3fc-d42c-4302-912a-638a4a38e4d4.png align="center")

* Copy the **content** of my [intune-backup-pipeline.yml](https://github.com/ztrhgf/DevOps_Pipelines/blob/main/intune-backup-pipeline.yml) and use it to **replace** the generated default code!
    

> If you want to create
> 
> * Markdown document describing your Intune environment, uncomment Task `Generate markdown document & commit`
>     
> * PDF and HTML documentation reports as artifacts, uncomment also whole `PUBLISH` job
>     
> 
> By default this pipeline will be started every day at 1 am. If you want to change that, just alter the cron key value (`cron: '0 1 * * *'`). You can use [https://crontab.guru](https://crontab.guru/every-1-hour) to generate a new one.

### Customize predefined pipeline variables

At the beginning of the pipeline code, there are some variables defined that need to be set to suit your environment.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1711552906266/de0c529b-b71a-4f12-a010-226fb6257342.png align="center")

* `TENANT_NAME`
    
    * **this has to be set to match your tenant!**
        
    * something like *contoso.onmicrosoft.com*
        
* `SERVICE_CONNECTION_NAME`
    
    * **name** of the workload identity service connection (`Service connection name`) created earlier
        
* `USER_EMAIL`
    
    * you can leave it as it is
        
    * default email that will be used as commits author email, something like *Intune\_Backupper@contoso.com* (or whatever you wish)
        
* `USER_NAME`
    
    * you can leave it as it is
        
    * default username that will be used as commits author, something like *Intune\_Backupper* (or whatever you wish)
        

### Save & Run the pipeline

Now when the pipeline is ready, save it by choosing `Save and run` . This way you will immediately see whether the pipeline is working fine 👍

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695725213481/ed73c420-13c0-4ca4-b165-d17b3503e3e3.png align="center")

After pipeline finishes, you should see that `prod-backup` folder was created and is filled with your Intune configuration JSON files 😍

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697529061555/ab0a27a1-4371-41e0-a2d5-dbd2172b4939.png align="center")

> According to the schedule, it will be automatically started by default every day at 1 a.m.

### What does this pipeline do?

* **Install IntuneCD** tool
    
* **Export Intune configuration** backup to `prod-backup` folder
    
* If a change in configuration is detected
    
    * **For each changed file find who did it**
        
        * by searching the Intune Audit log for particular ResourceId changes since the last config commit date
            
    * **Group changed files** by author(s) who changed them
        
    * **Commit each group separately** by using the author name(s) as the commit author(s) too
        
    * Create an Intune configuration documentation file (as markdown)
        
        * (only if you uncomment the task `Generate markdown document & commit`)
            
    * **Create Git TAG**
        
        * name of all changes authors will be included in the TAG description
            
    * Generate HTML and PDF files from the Intune configuration documentation file and save them as Pipeline Artifacts
        
        * (only if you uncomment task `Generate markdown document & commit`, job `Publish as-built artifacts` and add the folder `md2pdf` ([from this repository](https://github.com/aaronparker/intune-backup-template/tree/main/md2pdf)) to your repository root)
            
* If there is no change
    
    * Pipeline ends
        

> By default you have 1800 minutes of pipeline run per month for free. One Intune backup pipeline run takes approximately 2 minutes from this pool, so no problem here.

---

### Send a notification in case the pipeline fails

It is useful to be notified when the pipeline fails. For example because application secret expired etc. To do so go to `Project settings` &gt; `Notifications` &gt; `New Subscription` and select `Build` &gt; `A Build fails` end finish the wizard.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695729496101/aa55697f-9e7d-45b6-9b70-56bb17d145a8.png align="center")

---

# How to

Because backup is saved in the GIT repository, you can use commands you are familiar with (git log, git diff, git blame, etc) and don't be limited to GUI as shown below.

## Get changes made to the specific file

To get a list of all changes to the particular file. Open such file in the DevOps interface and choose `History` tab.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697731298275/faf803a5-4878-4271-9e8c-540d45470e79.png align="center")

To see what has changed between two commits/conf. backups. Choose `Compare` tab.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697462737588/d710edb6-57a3-4b0f-bb41-fac0450b5f8f.png align="center")

## Get changes made by a specific author

Because change author name is used as commit author and in commit name itself, you can search it by several ways. In DevOps gui, you can switch to `Commits` &gt; Click on `Show filter` button in top right corner

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697525622468/8c8be8c5-5863-4c67-a306-4b2b038a336a.png align="center")

And type author's name to `Author` field.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697525671149/55bb6d2f-887c-4acc-88fa-5676611aa698.png align="center")

> In GIT, you can use [git log command](https://stephencharlesweiss.com/filter-git-commits-by-author) to achieve the same

## Get changes made between specified dates

To get a visual list of changes made in your Intune configuration between specified dates, go to `Repos` &gt; `Tags` select the first Tag and `Set as compare tag`, then select the second one and run the comparison by `Compare tags`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695730049579/ce7fd9f4-3eda-4a1a-8f90-1b721c70206d.png align="center")

On the result page select `Files` and enjoy the view 🙂

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695731300484/55e305c5-76aa-4ce0-b196-3f8e44a16b15.png align="center")

<s>Strikethrough</s> files are settings that were deleted. Plus sign on the right side of the file means it is a new setting that was created.

## Show the Intune configuration status on a specific date

To see what your Intune environment looked like on a specific date. Go to `Repos` &gt; `Tags` &gt; Pick the nearest **older**`Tag` &gt; Open the folder `prod-backup` and browse the content (exported JSON configuration files).

This way you will see how your Intune looked like just before the specified date.

To see what changes were made during this time, compare **this**`Tag` with the nearest **newer**`Tag`.

> Beware that if someone made some changes in the configuration and reverted them back before the next backup occurred, they won't be shown (obviously).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1695736112513/afc4807d-2c66-40e9-bd4e-809ceafcef82.png align="center")

## Change what should be included in the backup

To modify what type of Intune configurations should be backed up, you must edit **Create Intune backup** task section in the Pipeline configuration code.

Go to `Pipelines` &gt; `<yourPipeline>` &gt; click `Edit` &gt; Find a task with the name **Create Intune backup** and modify its `script` section by modifying the [\--exclude](https://github.com/almenscorner/IntuneCD/wiki/Backup#arguments) parameter.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697456545884/ddb1a8b1-d488-4d3c-9a4b-26a0c87459db.png align="center")

By default, I am excluding `CompliancePartner`, because its data changes every day and I want new commits to be created only in case some real change happens.

## Restore the backup

When restoring the backup, you have basically two options.

1. Use the IntuneCD [update feature](https://github.com/almenscorner/IntuneCD/wiki/Update)
    
    1. create new folder
        
    2. copy just folder structure with JSON(s) you want to restore to this folder
        
    3. run `IntuneCD-startupdate` with `--path "pathToFolderWithBackup"`
        
2. Manually create the required Intune configuration(s) by filling in the values shown in your backup JSON(s)
    

---

# How the change author(s) are managed

In the GIT repository, if you want to see who changed file *XYZ*, you check who was the author of the commit that changed such file. Therefore it was only logical to use the same approach to "save" information about who has changed the Intune configuration (JSON file).

## How the change author is found?

When change is detected in the created backup (some JSON configuration file is changed) the pipeline will

* Get a list of all changed files
    
* Getting the last configuration backup commit date (`$lastCommitDate`)
    
    * if no commit exists whole Intune Audit log will be searched
        
* For each changed file find out who changed it by
    
    * retrieving resource ID from its name
        
    * searching such ID in the Intune Audit log (between dates saved in `$lastCommitDate` and start time of this backup run) and making note of all authors that made some changes there
        
* Group the changed files by found authors and make a commit for each group where the list of all authors that changed such files is used in the commit name and as the commit author
    
* Put all found authors into Git Tag description too
    

## Good to know

* The list of author(s) doesn't have to be 100% accurate. If you need 100% accuracy (because of security incident etc), use the command `Get-MgDeviceManagementAuditEvent`
    
    * for example when someone makes a change in the Intune right after the backup starts. Such change can be captured, but the author will not, because the Intune log is searched from the last commit date to the **start** of the backup task only. If the search ended at the backup **finish** instead, it could lead to an opposite situation when the author list could contain someone who didn't make the change that was captured in the backup.
        
* The list of commit authors shows who has made any changes to committed files (since the last commit). It doesn't show who made what particular change
    
* If a change was made by an application (instead of a user), the name of the application is used as the author name with `(SP)` suffix (for example *Modern Workplace Management (SP)*)
    
* Some Intune configuration changes aren't captured in the Intune Audit log at all! Like changes to ESP profile etc.
    
    * Therefore if the author of the changed configuration wasn't found in the Audit log, `unknown` is used instead
        
* Intune uses several formats of IDs! Not just `<GUID>` as you would imagine, but for example `ESP profile`, `Apple configurator profile` , `App Protection policy` uses `<GUID>_<GUID>`, `<GUID>_<somestring>`*,*`T_<GUID>` and there are probably some other crazy stuff
    

---

# Summary

If you finished all the required steps, you now have a working backup solution for your Intune configuration which you can easily use for tracking changes & blame the change authors 😎

Next time I will show you have to achieve the same thing for Azure configuration as well 👍

---

# Links

* [https://stealthpuppy.com/automate-intune-documentation-azure/](https://stealthpuppy.com/automate-intune-documentation-azure/)
    
* [https://gaunacode.com/passing-variables-between-jobs-for-azure-devops-pipelines](https://gaunacode.com/passing-variables-between-jobs-for-azure-devops-pipelines)
    
* [https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-a-multi-job-output-variable](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-a-multi-job-output-variable)
    
* [https://learn.microsoft.com/en-us/azure/devops/pipelines/process/conditions?view=azure-devops&tabs=yaml%2Cjobs](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/conditions?view=azure-devops&tabs=yaml%2Cjobs)
