In this post I would like to discuss a solution for automatic provisioning of Intune configuration.  we already know how to save an Intune configuration, but what if we want to import configuration from an existing repository?  

Scenario

You are an Intune administrator facing the following challenge. You need to quickly provision Intune configurations either for new customers or for training purposes. You already have the configuration that you need to replicate.

Solution

 Use a PowerShell Script to download all the Intune configuration files that you previously pushed into a GitHub repository, and to import them in Intune. We will use scripts published by Microsoft for this exercise.

We will cover the following

  • Create a GitHub developer token
  • Prepare the authentication tokens ( MS Graph and GitHub)
  • Download and import the configuration

Create a GitHub developer token

We will not go over how to create a GitHub repository nor how to add the content. In the below image you can see the repository that I am using for this post. I previously uploaded some Intune configurations in a json format.

 Now is the time to generate a developer token to programmatically access this content.

  • go to your account/Settings

Click Developer Settings /Personal access tokens

Select token permissions as you see below

  • Generate the token
  • Note the warning saying to make sure you copy and save the token

Prepare the authentication tokens

*Make sure you have the AzureAD module imported

Now with the repository token is time to write some code.

For this solution we need to consume  GitHub and to Microsoft Graph APIs. The first will be used to get the content that will be saved into Intune configuration.

We will use a Microsoft Intune PowerShell sample script for our demonstration.

The below code creates a Microsoft Graph token based on admin user credential. The remaining 2 functions are used to upload the policy to Intune.  I will not explain the Microsoft sample scripts.

function Get-AuthToken {

<#
.SYNOPSIS
This function is used to authenticate with the Graph API REST interface
.DESCRIPTION
The function authenticate with the Graph API Interface with the tenant name
.EXAMPLE
Get-AuthToken
Authenticates you with the Graph API interface
.NOTES
NAME: Get-AuthToken
#>

[cmdletbinding()]

param
(
    [Parameter(Mandatory=$true)]
    $User
)

$userUpn = New-Object "System.Net.Mail.MailAddress" -ArgumentList $User

$tenant = $userUpn.Host

Write-Host "Checking for AzureAD module..."

    $AadModule = Get-Module -Name "AzureAD" -ListAvailable

    if ($AadModule -eq $null) {

        Write-Host "AzureAD PowerShell module not found, looking for AzureADPreview"
        $AadModule = Get-Module -Name "AzureADPreview" -ListAvailable

    }

    if ($AadModule -eq $null) {
        write-host
        write-host "AzureAD Powershell module not installed..." -f Red
        write-host "Install by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt" -f Yellow
        write-host "Script can't continue..." -f Red
        write-host
        exit
    }

# Getting path to ActiveDirectory Assemblies
# If the module count is greater than 1 find the latest version

    if($AadModule.count -gt 1){

        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]

        $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }

            # Checking if there are multiple versions of the same module found

            if($AadModule.count -gt 1){

            $aadModule = $AadModule | select -Unique

            }

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

    else {

        $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

    }

[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null

[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null

$clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"

$redirectUri = "urn:ietf:wg:oauth:2.0:oob"

$resourceAppIdURI = "https://graph.microsoft.com"

$authority = "https://login.microsoftonline.com/$Tenant"

    try {

    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

    # 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 ($User, "OptionalDisplayableId")

    $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI,$clientId,$redirectUri,$platformParameters,$userId).Result

        # If the accesstoken is valid then create the authentication header

        if($authResult.AccessToken){

        # Creating header for Authorization token

        $authHeader = @{
            'Content-Type'='application/json'
            'Authorization'="Bearer " + $authResult.AccessToken
            'ExpiresOn'=$authResult.ExpiresOn
            }

        return $authHeader

        }

        else {

        Write-Host
        Write-Host "Authorization Access Token is null, please re-run authentication..." -ForegroundColor Red
        Write-Host
        break

        }

    }

    catch {

    write-host $_.Exception.Message -f Red
    write-host $_.Exception.ItemName -f Red
    write-host
    break

    }

}

Function Add-DeviceConfigurationPolicy(){

<#
.SYNOPSIS
This function is used to add an device configuration policy using the Graph API REST interface
.DESCRIPTION
The function connects to the Graph API Interface and adds a device configuration policy
.EXAMPLE
Add-DeviceConfigurationPolicy -JSON $JSON
Adds a device configuration policy in Intune
.NOTES
NAME: Add-DeviceConfigurationPolicy
#>

[cmdletbinding()]

param
(
    $JSON
)

$graphApiVersion = "Beta"
$DCP_resource = "deviceManagement/deviceConfigurations"
Write-Verbose "Resource: $DCP_resource"

    try {

        if($JSON -eq "" -or $JSON -eq $null){

        write-host "No JSON specified, please specify valid JSON for the Android Policy..." -f Red

        }

        else {

        Test-JSON -JSON $JSON

        $uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)"
        Invoke-RestMethod -Uri $uri -Headers $global:authToken -Method Post -Body $JSON -ContentType "application/json"

        }

    }

    catch {

    $ex = $_.Exception
    $errorResponse = $ex.Response.GetResponseStream()
    $reader = New-Object System.IO.StreamReader($errorResponse)
    $reader.BaseStream.Position = 0
    $reader.DiscardBufferedData()
    $responseBody = $reader.ReadToEnd();
    Write-Host "Response content:`n$responseBody" -f Red
    Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
    write-host
    break

    }

}

####################################################

Function Test-JSON(){

<#
.SYNOPSIS
This function is used to test if the JSON passed to a REST Post request is valid
.DESCRIPTION
The function tests if the JSON passed to the REST Post is valid
.EXAMPLE
Test-JSON -JSON $JSON
Test if the JSON is valid before calling the Graph REST interface
.NOTES
NAME: Test-AuthHeader
#>

param (

$JSON

)

    try {

    $TestJSON = ConvertFrom-Json $JSON -ErrorAction Stop
    $validJson = $true

    }

    catch {

    $validJson = $false
    $_.Exception

    }

    if (!$validJson){

    Write-Host "Provided JSON isn't in valid JSON format" -f Red
    break

    }

}


#region Authentication

write-host

# Checking if authToken exists before running authentication
if($global:authToken){

    # Setting DateTime to Universal time to work in all timezones
    $DateTime = (Get-Date).ToUniversalTime()

    # If the authToken exists checking when it expires
    $TokenExpires = ($authToken.ExpiresOn.datetime - $DateTime).Minutes

        if($TokenExpires -le 0){

        write-host "Authentication Token expired" $TokenExpires "minutes ago" -ForegroundColor Yellow
        write-host

            # Defining User Principal Name if not present

            if($User -eq $null -or $User -eq ""){

            $User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication"
            Write-Host

            }

        $global:authToken = Get-AuthToken -User $User

        }
}

# Authentication doesn't exist, calling Get-AuthToken function

else {

    if($User -eq $null -or $User -eq ""){

    $User = Read-Host -Prompt "Please specify your user principal name for Azure Authentication"
    Write-Host

    }

# Getting the authorization token
$global:authToken = Get-AuthToken -User $User

} 
  • Note the last line of the script where we generate the MS Graph Token and store it in a global variable

The second token we need to create will be used to connect to GitHub, list repositories and download files.

  • Create the token string using the GitHub dev token you previously saved. The string has this format <GitHubuser>:<DeveloperToken>
  • This token will be encoded to Base64 before attempting the authentication
  • Construct a header with the encoded token
  • Now we can consume the GitHub API with the newly created token.
  • In our script we get the repositories and the config files and store them in $repos and $configfiles as you can see below
#Enable SSL
[Net.ServicePointManager]::SecurityProtocol [Net.SecurityProtocolType]::Tls12

$Tkn ='gabim101:b1exxxxxxxxxxxxbb0c1be3c39453c30191'
$base64Token  = [System.Convert]::ToBase64String([char[]]$Tkn);
$Headers = @{
    Authorization = 'Basic {0}' -f $base64Token;
    };
    
$gitrepos ="https://api.github.com/user/repos"

$gitconfig = "https://api.github.com/repos/gabim101/intune-content/contents/Config"

$repos= Invoke-RestMethod -Headers $Headers -Uri $gitrepos -Method Get
$configfiles = Invoke-RestMethod -Headers $Headers -Uri $gitconfig -Method Get 

Now, is the time to import the content. If you followed this far your $configfiles variable will have a list of objects like this one:

PS C:\WINDOWS\system32> $configfiles


name         : AppAssociation_25-09-2019-17-44-18.json
path         : Config/AppAssociation_25-09-2019-17-44-18.json
sha          : 627e86e27d8e3bac5096eabc06a5c82e05a71014
size         : 2671
url          : https://api.github.com/repos/gabim101/intune-content/contents/Config/AppAssociation_25-09-2019-17-44-18.json?ref=master
html_url     : https://github.com/gabim101/intune-content/blob/master/Config/AppAssociation_25-09-2019-17-44-18.json
git_url      : https://api.github.com/repos/gabim101/intune-content/git/blobs/627e86e27d8e3bac5096eabc06a5c82e05a71014
download_url : https://raw.githubusercontent.com/gabim101/intune-content/master/Config/AppAssociation_25-09-2019-17-44-18.json
type         : file
_links       : @{self=https://api.github.com/repos/gabim101/intune-content/contents/Config/AppAssociation_25-09-2019-17-44-18.json?ref=master; 
               git=https://api.github.com/repos/gabim101/intune-content/git/blobs/627e86e27d8e3bac5096eabc06a5c82e05a71014; html=https://github.com/gabim101/intune-content/blob/master/Config/AppAssociation_25-09-2019-17-44-18.json} 

The content we want is in the git_url field and is Base64 encoded.

For all the config files we need to:

  • download the config
  • decode
  • clean the json policies of fields that will be auto-created when we import the policy
  • add the policy to Intune using Microsoft function we already have in our script

See below the script :

foreach ( $conf in $configfiles ){

    $filename = $conf.name
    $encoded = Invoke-RestMethod -Headers $Headers -Uri $conf.git_url -Method Get
    $decoded = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($encoded.content))

    $cleanPolicy = ConvertFrom-Json $decoded | Select-Object -Property * -ExcludeProperty id,createdDateTime,lastModifiedDateTime,version,supportsScopeTags
    $pol = ConvertTo-Json $cleanPolicy -Depth 5 
    
    Write-Progress $conf.name
    Add-DeviceConfigurationPolicy -JSON $pol 
    
}

We can put together the entire script now and see what happens

If you are using a new tenant accept the requested permissions to move on

Once the authentication finishes the import will start and you will see something similar with the below image if using PowerShell ISE

If you log into Intune, you see the Device Configurations profiles imported

Summary

In this post we discovered how to import configurations into Intune using content saved in a GitHub repository. This example can be easily extended to importing other objects as compliance policies, associations etc.