Category Archives: AzureAD

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='https://graph.windows.net/';grant_type='client_credentials'}
$headers = @{"Authorization" = "Bearer $((invoke-webrequest -uri "https://login.microsoftonline.com/$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 "https://graph.windows.net/$tenantId/applications?api-version=1.6&`$filter=displayName eq %27$appName%27" -Headers $headers -ContentType "application/json").value
if(!$app){
    $body = [pscustomobject]@{
	    'displayName' = $appName
        'availableToOtherTenants' = $True
    }
    $app = Invoke-RestMethod -Method POST -UseBasicParsing -Uri "https://graph.windows.net/$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 "https://graph.windows.net/$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 "https://graph.windows.net/$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 "https://graph.windows.net/$tenantId/servicePrincipals?api-version=1.6&`$filter=displayName eq %27$appName%27" -Headers $headers -ContentType "application/json").value
if(!$sp){
    ##Adding service principal to application instance
    $body = [pscustomobject]@{
	    'appId' = $app.appId
        'tags' = @("WindowsAzureActiveDirectoryIntegratedApp")
    }
    $sp = Invoke-RestMethod -Method POST -UseBasicParsing -Uri "https://graph.windows.net/$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:

	Connect-AzureAD
	$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 🙂

Azure AD sign in and audit log retention

Often we, as cloud admins, need our audit or sign in logs. Usually, we need real-time data because, for example, we’re debugging why that one user has conditional access issues. But sometimes, we need to go back further than 30 days. And that is not something Azure does by default, but can be enabled:


Our options when exporting logs are limited to a Storage account, Log Analytics or an Event Hub. All these options offer multiple extraction methods to cover your transport needs to other systems. The default retention period is then forever, which is nice as we might need audit info going back a bit as hacks are usually discovered after about 206 days.

If you don’t have specific tools or requirements, I recommend setting up a Log Analytics workspace and connecting that to Azure AD:

Whichever method you choose, a P1 or P2 license is required. You only need a single license for the entire tenant when using the export audit / singin log functionality of AzureAD. Once configured, the Logs option directly bring you to the Log Analytics workspace search results:

I’ve briefly shown how to configure AzureAD to send audit and sign in logs to Log Analytics so you can go back further than 30 days. Stay tuned for the next post that will utilize these logs to dive deeper into Guest User activity.

Devices that lack a bitlocker recovery key in AzureAD

With Intune’s new Bitlocker Encryption Report administrators have an effective way of seeing which of their devices have been encrypted.

But if we want to know if we can actually recover the bitlocker key of a device, we need to know if it was ever uploaded to AzureAD.

Network or local device issues can sometimes prevent the recovery key from reaching AzureAD, resulting in lost data if the device’s disk needs to be recovered for any reason. To hunt down devices that have not escrowed their recovery key to AzureAD, you can use my report function (in PowerShell as always):

GitLab source download link