Category Archives: AzureAD

Using pipeline identity for Connect-AzureAD, Graph and other endpoints

Azure Pipelines and Azure Functions (and Automation Accounts) can have managed identities, in other words, a service principal. This service principal can be assigned to Azure AD roles (e.g. to modify users / devices) or graph / Azure RM resources. A service principal could even be a global admin, and Service Principals don’t have to do MFA…. 🙂

In both Pipelines and Functions the new Az module is enabled and logged into your tenant by default as the service principal, how cool would it be to use that identity to do those (hopefully few) things that are still only supported by e.g. the AzureAD module?

Here’s an example on getting tokens for Azure AD and for Graph, obviously you could also get tokens for other audiences the same way:

$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "").AccessToken
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "").AccessToken

Write-Output "Hi I'm $($context.Account.Id)"

Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $

get-azureaduser -Top 5

If you want to do this from an old fashioned Azure Runbook (please move to functions!) then you’ll have to log in to Az first:

    $servicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnection"
    Connect-AzAccount -Tenant $servicePrincipalConnection.TenantID `
        -ApplicationId $servicePrincipalConnection.ApplicationID   `
        -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `
}catch {
    Write-Error -Message $_.Exception
    throw $_.Exception

Duplicate AzureAD Device Cleanup

When you swap a device by reimaging or reinstalling, the Hardware ID stays the same. This results in multiple Device Entries in Azure AD and causes issues with Conditional Access as Intune thinks the older version isn’t actually compliant even though Intune just has 1 record.

Most methods (such as Nicola’s) to combat this is by cleaning up stale devices in Azure AD based on their last Active Date. However, the downside of this method is that it may touch devices which weren’t duplicates, just dormant during, e.g. a vacation. Additionally, a bug in AzureAD can cause the older duplicate’s active date to be updated instead of the correct device.

The following script detects duplicates based on the Hardware ID and registration date instead and disables all but the most recent entry. It can supplement stale device removal based on Last Activity.

Note: only works for Windows registered devices.

Git: disable-duplicateAzureAdDevices.ps1

Azure AD app registration and scope + roles modification (manifest) as application

As the ‘new’ Graph API does not support application credentials (client_credentials oauth2 flow) when working with most of the ServicePrincipal and Application parts of the Graph API, and I really did not want to work with a user account (background processes, MFA, etc), I had to work something out on some of the older (but still supported) API’s which I gleaned from Msft’s PS modules.

For anyone googling, the following code example allows you to create an azure ad application with serviceprincipal and allows you to modify the manifest of the application (e.g. here is the AppRoles and AppIdentifier).

$tenantId = "75d24247-6221-46a1-a651-530ae36dd399"
$clientId = "62d2235b-2ef6-4d70-b273-401c9eb450b3" #client ID (to call graph api with)
$clientSecret = "xxxxxx" #client secret

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Web")
$body = @{client_id=$clientId;client_secret=$clientSecret;resource='';grant_type='client_credentials'}
$headers = @{"Authorization" = "Bearer $((invoke-webrequest -uri "$tenantId/oauth2/token" `
-Method POST -ContentType "application/x-www-form-urlencoded" -Body $body).Content | convertfrom-json | select access_token -ExpandProperty access_token)"

#finding an application or create it if it doesn't exist
$appName = "MyApplication"
$app = (Invoke-RestMethod -Method GET -UseBasicParsing -Uri "$tenantId/applications?api-version=1.6&`$filter=displayName eq %27$appName%27" -Headers $headers -ContentType "application/json").value
    $body = [pscustomobject]@{
	    'displayName' = $appName
        'availableToOtherTenants' = $True
    $app = Invoke-RestMethod -Method POST -UseBasicParsing -Uri "$tenantId/applications?api-version=1.6" -Headers $headers -Body ($body | ConvertTo-Json -Depth 100) -ContentType "application/json"

#check/correct the identifier URI of the application's published API
if($app[0].identifierUris -notcontains "api://mydomain/myApi"){
    $body = [PSCustomObject]@{
        "identifierUris" = @("api://mydomain/myApi")
    Invoke-RestMethod -Method PATCH -UseBasicParsing -Uri "$tenantId/applications/$($app[0].objectId)?api-version=1.6" -ContentType "application/json" -Headers $headers -Body ($body | ConvertTo-Json -Depth 100)

#check if a certain approle is present, if not, add it
if(@($app[0].appRoles | Where{$_.displayName -eq "Access To My API"}).Count -eq 0){
    $body = [PSCustomObject]@{
        "appRoles" = @([PSCustomObject]@{
            "allowedMemberTypes" = @("Application","User")
            "description" = "Access To My API"
            "displayName" = "Access To My API"
            "id" = [Guid]::NewGuid()
            "isEnabled" = $True
            "value" = "Api.AccessRead"
    Invoke-RestMethod -Method PATCH -UseBasicParsing -Uri "$tenantId/applications/$($app[0].objectId)?api-version=1.6" -ContentType "application/json" -Headers $headers -Body ($body | ConvertTo-Json -Depth 100)

#finding the serviceprincipal belonging to the application or creating it if it doesn't exist
$sp = (Invoke-RestMethod -Method GET -UseBasicParsing -Uri "$tenantId/servicePrincipals?api-version=1.6&`$filter=displayName eq %27$appName%27" -Headers $headers -ContentType "application/json").value
    ##Adding service principal to application instance
    $body = [pscustomobject]@{
	    'appId' = $app.appId
        'tags' = @("WindowsAzureActiveDirectoryIntegratedApp")
    $sp = Invoke-RestMethod -Method POST -UseBasicParsing -Uri "$tenantId/servicePrincipals/?api-version=1.6" -Headers $headers -Body ($body | ConvertTo-Json -Depth 100) -ContentType "application/json"

Note that, for this code to work, you need to grant your application the Company Administrator role, like this:

	$app = Get-AzureADServicePrincipal -SearchString "myapplication"
	$role = Get-AzureADDirectoryRole | Where-Object { $_.DisplayName -eq "Company Administrator" }
Add-AzureADDirectoryRoleMember -ObjectId $role.ObjectId -RefObjectId $app.ObjectId

More licenses and features

Pivot table of all Microsoft cloud suites and their features

I’ve updated the Microsoft cloud suites feature comparison page with all other suites Microsoft including all their features. I’ve also added all Education sku’s. You can use the pivot table to sort / mix / match according to your exact needs. If you need any assistance with Microsoft 365, don’t be a stranger 🙂