With the upcoming release of Microsoft Intune in the Azure portal, we’re finally getting support for automation. Microsoft states that everything that you can do through the Azure portal, is possible to accomplish with PowerShell as well. This becomes possible because Microsoft has built the new portal on top of what’s called Microsoft Graph API. If you were to add a new Device Profile, add an App or create a Compliance Policy, all the actions you take within the portal, is actually being processed by the Microsoft Graph API that communicates with the Microsoft Intune backend. With this knowledge in mind, it’s now possible to start exploring all the possibilities available through the Microsoft Graph API and how it can be used with PowerShell.

Keep in mind, this does not mean that there will initially be a PowerShell module available for Microsoft Intune. Organizations would have to create their own set of scripts or applications that accesses data directly through Microsoft Graph API. For some this might be seen as a disadvantage, or poor choice from Microsoft, however we have finally been given the necessary tools required for automation, which in my opinion is a good start.

NOTE: Microsoft Intune in Azure is currently in Public preview and tenants are being migrated over a period of time. If your tenant has not yet been migrated, you can create a test tenant for the purpose of getting started with automation in Intune. In addition, pay attention to the graph.microsoft.com/beta pointers in this post. When in production, this should be changed to v1.0.

In this blog post we’ll take a look at how we can get started with automating tasks in Intune. But before we go any further, read through the following sections to get a better understanding of how automation in Intune works.

What is Microsoft Graph API

Microsoft Graph API is a publicly available API that provides access to your data, not only for data in Intune but also other cloud services from Microsoft. It serves as a single interface where cloud services can be reached through a set of REST APIs. This gives developers and IT Professionals a choice when managing administration and operation of for instance both Intune and Azure AD in an automated way. Simply put, Microsoft Graph API is the single interface required for automating productivity, such as mail, calendar, contacts documents, directories and not least devices.

Microsoft Graph API documentation for Intune

Before digging into writing scripts or applications for automating operation and/or administration tasks in Intune, take some time to explore the possibilities available through the Intune Graph API (subset of Microsoft Graph API). Intune Graph API references can be found on the following link:

https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/resources/intune_graph_overview

Authentication

When automating for other cloud services like Azure AD for instance, you have to authenticate with an identity. The Azure AD module already contains a cmdlet in PowerShell that takes care of this, but when accessing Microsoft Graph API directly, this process is a bit different. A quick search on google will reveal several methods and sample scripts that demonstrates how this is accomplished. What you need is to get your hands on what’s referred to as a Bearer token (also called Access Token). Retrieving this access token can, like google suggests, can be performed in several ways.

What we need to get our hands on is something that looks like this:

The picture above is showing a snippet of an bearer token. With this at hand, we can use this to construct the header data required when leveraging the Invoke-RESTMethod cmdlet, which is what we need to use when communicating with Microsoft Graph API. In this blog post, I’ll demonstrate two different methods and show you the required PowerShell code for retrieving the bearer token.

The two methods I’m referring to are:

  • Acquiring an Bearer Token by using well-known Client ID and Redirect URI from Azure PowerShell module
  • Acquiring an Bearer Token by calling the AzureAD PowerShell module’s dll with an existing Azure AD Application Registration
Using the well-known Client ID and Redirect URI for Azure PowerShell

This method does not require any external components or loading of any install modules, it simply queries for the bearer token (remember, also called access token) by using the Invoke-WebRequest cmdlet. Below is a function that I’ve written that could be used when retrieving the bearer token.

function Get-MSGraphAuthenticationTokenSilent {
    <#
    .SYNOPSIS
        Get an authentication token required for interacting with Microsoft Intune using Microsoft Graph API
        NOTE: This method does not support to retrieve an authentication token for accounts that are enabled with MFA

    .PARAMETER Credential
        Credential object containing user name and password for authentication against Azure AD.

    .PARAMETER TenantName
        A tenant name should be provided in the following format: tenantname.onmicrosoft.com

    .EXAMPLE
        Get-MSGraphAuthenticationTokenSilent -TenantName domain.onmicrsoft.com

    .NOTES
    Author:      Nickolaj Andersen
    Contact:     @NickolajA
    Created:     2017-03-20
    #>
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true, HelpMessage="Credential object containing user name and password for authentication against Azure AD.")]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCredential]$Credential,

        [parameter(Mandatory=$true, HelpMessage="A tenant name should be provided in the following format: tenantname.onmicrosoft.com")]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName
    )
    # Convert secure string to plain text
    $BasicString = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password) 
    $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BasicString)
    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BasicString)

    # Define body passed in POST method
    $Body = "resource=https://graph.microsoft.com/&client_id=1950a258-227b-4e31-a9cf-717495945fc2&grant_type=password&username=$($Credential.UserName)&scope=user_impersonation&password=$($PlainPassword)" 

    # Invoke POST method with constructed body
    $WebResponse = Invoke-WebRequest -Uri "https://login.microsoftonline.com/$($TenantName)/oauth2/token" -Method POST -Body $Body

    # Convert response from web request to JSON
    $JSONWebResoponse = $WebResponse | ConvertFrom-Json

    # Construct dictionary for authentication header
    $AuthenticationHeader = New-Object -TypeName "System.Collections.Generic.Dictionary``2[System.String,System.String]"
    $AuthenticationHeader.Add("Authorization", "Bearer $($JSONWebResoponse.access_token)")

    # Construct authentication hash table for holding access token and header information
    $Authentication = @{
        "Token" = $JSONWebResoponse.access_token
        "Header" = $AuthenticationHeader
    }
    
    # Return authentication hash table
    return $Authentication
}

NOTE: Remember to change the above variable called TenantName.

In the above code, a set of credentials together with the tenant name is passed as a parameter that’s used when constructing the body payload. It’s necessary to decrypt the password stored in the credential object when passing it, otherwise it would throw an error. Further along in the script, Invoke-WebRequest is called with login.microsoftonline.com as the URI (base URL), and this is where the tenant name comes into place. It’s important that you define the authority, so that the authorization goes to the correct tenant. A response from the web request is then returned as JSON, which we need to convert into a PowerShell object. The Authorization header required for Invoke-RESTMethod calls is then created and added into a hash table together with the access token. At this point the required PowerShell objects exists in the variable called Authentication, and can be accessed at any time later in a script.

There are a few considerations that you should be aware of when using this method of acquiring the bearer token. There’s no support for Multi-Factor Authentication, which is a good practice to have enabled on accounts with for instance Global Admin permissions. This method does although provide an unattended way of performing the task at hand, which in some scenarios might be required (scheduled tasks for instance).

Using AzureAD PowerShell module with an existing Azure AD Application Registration or Azure AD PowerShell well-known Client ID and Redirect URI

Apart from the previous method mentioned in this post, retrieving the bearer token through an Azure AD Application Registration (Web or Native Application), or by using the well-known Client ID and Redirect URI, could also be done by leveraging a Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext object from the AzureAD PowerShell module. This however, unlike the previous method, requires the AzureAD module to be available when the script is invoked. Installing the AzureAD module can easily be done by running the following command, where PowerShellGet is available:

Install-Module -Name AzureAD

Retrieving the bearer token with this method, does however support Multi-Factor Authentication, but may not be a suitable candidate when attempting to create an unattended script. Below is the function that I’ve written, suitable for this scenario.

function Get-MSGraphAuthenticationToken {
    <#
    .SYNOPSIS
        Get an authentication token required for interacting with Microsoft Intune using Microsoft Graph API
        NOTE: This function requires that AzureAD module is installed. Use 'Install-Module -Name AzureAD' to install it.

    .PARAMETER TenantName
        A tenant name should be provided in the following format: tenantname.onmicrosoft.com.

    .PARAMETER ClientID
        Client ID for Azure AD application. Leave empty to leverage Azure PowerShell well known client ID.

    .PARAMETER RedirectUri
        Redirect URI for Azure AD application. Leave empty to leverage Azure PowerShell well known redirect URI.

    .EXAMPLE
        Get-MSGraphAuthenticationToken -TenantName domain.onmicrsoft.com

    .NOTES
    Author:      Nickolaj Andersen
    Contact:     @NickolajA
    Created:     2017-03-20
    #>
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true, HelpMessage="A tenant name should be provided in the following format: tenantname.onmicrosoft.com.")]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName,

        [parameter(Mandatory=$false, HelpMessage="Client ID for Azure AD application. Leave empty to leverage Azure PowerShell well known client ID.")]
        [ValidateNotNullOrEmpty()]
        [string]$ClientID = "1950a258-227b-4e31-a9cf-717495945fc2",

        [parameter(Mandatory=$false, HelpMessage="Redirect URI for Azure AD application. Leave empty to leverage Azure PowerShell well known redirect URI.")]
        [ValidateNotNullOrEmpty()]
        [string]$RedirectUri = "urn:ietf:wg:oauth:2.0:oob"
    )

    # Load assemblies
    try {
        $AzureADModulePath = Get-InstalledModule -Name "AzureAD" | Select-Object -ExpandProperty InstalledLocation

        $Assemblies = @(
            (Join-Path -Path $AzureADModulePath -ChildPath "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"),
            (Join-Path -Path $AzureADModulePath -ChildPath "Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll")
        )
        Add-Type -Path $Assemblies -ErrorAction Stop

        # Construct new authentication context
        try {
            $Authority = "https://login.microsoftonline.com/$($TenantName)/oauth2/token"
            $ResourceRecipient = "https://graph.microsoft.com"
            $AuthenticationContext = New-Object -TypeName Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext -ArgumentList $Authority
            $AuthenticationToken = $AuthenticationContext.AcquireToken($ResourceRecipient, $ClientID, $RedirectUri, "Always")

            # Construct dictionary for authentication header
            $AuthenticationHeader = $AuthenticationToken.CreateAuthorizationHeader()

            # Construct authentication hash table for holding access token and header information
            $Authentication = @{
                "Token" = $AuthenticationToken.AccessToken
                "Header" = @{"Authorization" = $AuthenticationHeader}
            }

            # Return the authentication token
            return $Authentication
        }
        catch [System.Exception] {
            Write-Warning -Message "An error occurred when constructing an authentication token: $($_.Exception.Message)" ; exit
        }
    }
    catch [System.Exception] {
        Write-Warning -Message "Unable to load required assemblies (Azure AD PowerShell module) to construct an authentication token. Error: $($_.Exception.Message)" ; exit
    }
}

The main differences with this function compared with the previous, is that it doesn’t require a credential object to be passed as a parameter. It also gives you the option to specify your organizations Client ID and Redirect URI for an Azure AD Application Registration. Also, Invoke-WebRequest is replaced by calling the AquireToken method of the Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext object. Instead of manually creating the authorization header and storing it in a Dictionary object, a method called CreateAuthorizationHeader is invoked.

Getting data from Microsoft Intune with PowerShell

With the authentication part in mind, let’s see how we can put this bearer token to use, with a simple but yet working sample script. In the script below, I’m using the function that requires the AzureAD PowerShell module.

$AuthToken = Get-MSGraphAuthenticationToken -TenantName "tenant.onmicrosoft.com"

# Get a list of applications
$RESTResponse = Invoke-RestMethod -Method Get -Headers $AuthToken["Header"] -Uri https://graph.microsoft.com/beta/deviceAppManagement/mobileApps
if (-not[string]::IsNullOrEmpty($RESTResponse.Value)) {
    foreach ($Application in $RESTResponse.Value) {
        $Application.displayName
    }
}

NOTE: Remember to change the tenant name specified for the TenantName parameter.

First off we’ll get a prompt asking for the credentials that should be used when accessing Microsoft Graph (apologies for the Swedish text).

Bearer token is stored in the AuthToken variable and then used in the Invoke-RESTMethod cmdlet for the Headers parameter. In this example, a simple GET method is invoked to return a list of available apps in Intune. The URI parameter specifies the query invoked in Microsoft Graph. Depending on the operation your script is performing, this should reflect what’s documented in the Intune Graph API subsection at docs (link available earlier in this post).

Finally, once data has been returned from Microsoft Graph, a list of applications appear, which was the intention with this sample script:

Summary

We’ve now seen how we can leverage PowerShell to automate administration and/or operational tasks in Microsoft Intune, and I’m very excited to see what Microsoft has in store for us. Keep in mind, this might seem like a steep hill to climb if you’ve just started using PowerShell, but it only gets easier once you’ve played around with it a bit. This post was meant for giving an overview of how it works, and showing you and example that it actually works. In upcoming posts, I’ll continue to share my experiences with automating Microsoft Intune.

(4371)

Nickolaj Andersen

Principal Consultant and Enterprise Mobility MVP since 2016. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows devices and deployments including automation. Currently working for TrueSec as a Principal Consultant. Awarded as PowerShell Hero in 2015 by the community for his script and tools contributions. Creator of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService to name a few. Frequent speaker at conferences and user groups.

comments
  • Patrick Kaak
    Posted at 13:24 July 21, 2017
    Patrick Kaak
    Reply
    Author

    Hi Nickolaj,
    I’m trying to do silent authentication to the graph interface to connect to azure like in this blog. But I always get an error trying to get devices stating that I need a different scope.

    Response content:
    {
    “error”: {
    “code”: “Forbidden”,
    “message”: “Application is not authorized to perform this operation. Application must have one of the following scopes: DeviceManagementManagedDevices.Read.All, DeviceManagementManagedDevices.ReadWr
    ite.All – Operation ID (for customer support): 00000000-0000-0000-0000-000000000000 – Activity ID: 7694b3df-1a0d-46ba-aae0-6d67c495185a – Url: https://fef.amsub0102.manage.microsoft.com/DeviceFE/Statele
    ssDeviceFEService/managedDeviceOverview?api-version=2017-05-18 – CustomApiErrorPhrase: “,
    “innerError”: {
    “request-id”: “7694b3df-1a0d-46ba-aae0-6d67c495185a”,
    “date”: “2017-07-21T12:00:07”
    }

    When I change the scope in the script, you still get the impersonate_user scope back with the app id. So I changed it to mine and added the secret key to it. After that I always get a eduAdministration scope and not the requested scope.
    Name Value
    —- —–
    Scope EduAdministration.ReadWrite User.Read
    Authorization Bearer eyJ0eXAiO…
    Content-Type application/json
    ExpiresOn 1500642113

    When I do the same interactive I get the right scope back. Any idea how to get that silent authentication working?

  • Leave a Reply