Creating an Azure AD Application and Granting Application Admin Consent with Powershell

Written by James McDonald

January 28, 2022

Standing on the shoulders of giants here is a script that creates an Azure AD Application grants it access to the MS Graph API and the Mail.Send role ability.

It could be modified to grant access to different resources and permissions

There are two functions that can do an admin consent for Application Permissions



The first function specifically approves the Application to use Microsoft Graph Mail.Send role with this function you would need to loop through each role to approve each one. The Grant-OAuth2PermissionsToApp function will approve multiple roles at once. I am using auth tokens taken from the context or Get-AzAccessToken check the code for specifics.

I logged in as a Global Administrator to run this script.

# run as admin
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {   
    $arguments = "& '" + $myinvocation.mycommand.definition + "'"
    Start-Process powershell -Verb runAs -ArgumentList $arguments

function logThis ($text, $logFile) {
    "[$(Get-Date -format 'yyyy-MM-dd HH:mm:ss')] $($text)" | out-file $logFile -append

# place to save output
$tmpPath = "C:\Temp\Test"
$appName = "My Test App 6";

#Setup Logging
$logFile = "$($tmpPath)\AppInstallationLog.txt"
if (!(Test-Path "$($tmpPath)")) { New-Item -ItemType directory -path "$($tmpPath)" }

logThis "[INFO] ******************* SCRIPT START *******************" $logFile


#Define Functions:
# working

 This function can approve an individual role
 So in this case it is approving the application to access the Microsoft Graph API and specifically Mail.Send
 This is NOT doing delegated approval but Application approval (you need this so that a program can send email as any user)
Function Grant-AdminConsentPermissionsToApp {
    $token = (Get-AzAccessToken -ResourceUrl "").Token
    $msGraphObjectId = (Get-AzureADServicePrincipal -All $true | Where-Object { $_.DisplayName -eq "Microsoft Graph" }).ObjectId
    $apiUrl = "$($msGraphObjectId)/appRoleAssignments"

    # The Object ID of the service principal
    $principalId = (Get-AzureADServicePrincipal -All $true | Where-Object { $_.AppId -eq $newAppId }).ObjectId
    # Mail.Send Id
    $appRoleId = ((Get-AzureADServicePrincipal -All $true | Where-Object { $_.DisplayName -eq "Microsoft Graph" }).AppRoles | 
        Where-Object { $_.Value -eq "Mail.Send" }).Id

    $body = @{
        principalId = $principalId
        resourceId  = $msGraphObjectId
        appRoleId   = $appRoleId

    Invoke-RestMethod -Uri $apiUrl -Headers @{Authorization = "Bearer $($token)" }  -Method POST -Body $($body | convertto-json) -ContentType "application/json"

Function Grant-OAuth2PermissionsToApp {
    $context = Get-AzContext
    if ($null -eq $context) {
        $null = Connect-AZAccount -EA stop
        $context = Get-AzContext
    #  get an access token to access resource / 74658136-14ec-4630-ad9b-26e160ff0fc6
    $token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id, $null, "Never", $null, "74658136-14ec-4630-ad9b-26e160ff0fc6")

    $header = @{
        'Authorization'          = 'Bearer ' + $token.AccessToken
        'X-Requested-With'       = 'XMLHttpRequest'
        'x-ms-client-request-id' = [guid]::NewGuid()
        'x-ms-correlation-id'    = [guid]::NewGuid()
    $url = "$newAppId/Consent?onBehalfOfAll=true"

    Invoke-RestMethod -Uri $url -Headers $header -Method POST -ErrorAction Stop

if (Get-Module -ListAvailable -Name AzureAD) 
else {
    Install-Module -Name AzureAD
    logThis "[INFO] Installing Module AzureAD" $logFile

Write-Host                          "Please Log in with an Azure AD Admin Account"

Write-Host                          "Creating Application with Mail.Send Permissions within your Azure AD Tenant...Please wait..."

$tenant = Get-AzureADTenantDetail

# first get the MS Graph service principal
$msGraphPrincipal = Get-AzureADServicePrincipal -All $true | Where-Object { $_.DisplayName -eq "Microsoft Graph" }
$requiredResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$requiredResourceAccess.ResourceAppId = $msGraphPrincipal.AppId

$msgraphRole = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList "b633e1c5-b582-4048-a93e-9f11b44c7e96", "Role"
# Duplicate each role here you want to add here
#$msgraphRole2 = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList "GUID of Role look in manifest in the portal to find this", "Role"

$requiredResourceAccess.ResourceAccess = $msgraphRole #, $msgraphRole2 pass each role  you want to add on this line as an array

$newApp = New-AzureADApplication -DisplayName $appName -RequiredResourceAccess @($requiredResourceAccess)

logThis "[INFO] Application Created" $logFile

# password with 5 year expiry
$appSecret = New-AzureADApplicationPasswordCredential -ObjectId $newApp.ObjectId -CustomKeyIdentifier "$($appName) Key" -EndDate (get-date).AddYears(5)

logThis "[INFO] Secret Key Created" $logFile

# needed for granting admin consent
# create a service principal and associate it with the Azure Application

$svcPrincipal = New-AzureADServicePrincipal -AppId $newApp.AppId -Tags @("WindowsAzureActiveDirectoryIntegratedApp")

logThis "[INFO] Service Principal Created" $logFile

$newAppId = $newApp.AppId
$tenantId = $tenant.ObjectId
$appSecretValue = $appSecret.Value
$tenantDomain = ($tenant.VerifiedDomains | ? { $_._Default -eq $true }).Name

logThis "[INFO] Sleep Started" $logFile
Write-Host "Sleeping 30 seconds"
Start-Sleep -s 30

# this works to do app consent for individual roles but you would need to loop through each role to consent
# Grant-AdminConsentPermissionsToApp

logThis "[FUNCTIONCALL] Grant-OAuth2PermissionsToApp" $logFile
# this bulk approves all the roles

#Write-Out Access Details to Log File
logThis "Application Created Successfully" $logFile
logThis "Application (client) ID: " + $newAppId $logFile
logThis "Directory (tenant) ID: " + $tenantId $logFile
logThis "Client Key: " + $appSecretValue $logFile
logThis "Tenant Domain: " + $tenantDomain $logFile

#Error Collection
if ($error.count -gt 0) {
    logThis "[WARN] Error Count: " + $error.count $logFile
    logThis "[WARN] Error Text: " $logFile
    logThis $error $logFile
    logThis "*** End Error Text ***" $logFile
{ logThis "[INFO] No Errors" $logFile }

logThis "[INFO] ******************* SCRIPT END *******************" $logFile

Write-Host                          "Application successfully created..."
Write-Host                          " "
Write-Host                          "Application (client) ID:" $newAppId
Write-Host                          "Directory (tenant) ID:" $tenantId
Write-Host                          "Client Key:" $appSecretValue
Write-Host                          "Tenant Domain:" $tenantDomain
Write-Host                          " "
Write-Host                          "Please store the following file safely (i.e. encrypted somewhere):"
Write-Host                          "$($logFile)"


  1. Kristy

    Thank you so much! I don’t even want to count the hours I spent trying to get this working via powershell. Really appreciate you working this our and writing it up!


Submit a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

You May Also Like…

Clear HSTS Settings in CHrome

Open chrome://net-internals/#hsts enter the domain in the query field and click Query to confirm it has HSTS settings...

Ubuntu on Hyper-v

It boils town to installing linux-azure # as root or sudo apt-get update apt-get install linux-azure...