Microsoft Entra ID

Get app details and grant permissions to app registration

Provision App Registration

App Registrations are containers that allow user-independent permission assignment and are therefore ideally suited for automation. App Registrations can be created in Azure Portal → Microsoft Entra ID → App Registrations. App Registrations should be preferred to service accounts whenever possible.

The following variables are used to authenticate to the Graph API using application permissions. The ClientSecret must not be stored as clear text in scripts or applications under any circumstances, but must be stored in designated containers (e.g. Azure Runbook Credential Store, Azure Key Vault or Windows Credential Store).

Variables used

$TenantID: This is the identity of the tenant, which is unique.

$ClientID: The ClientID can be used to uniquely identify the App Registration.

$ClientSecret: The ClientSecret expires every max. 24 months (2 years) and is like the password for the App Registration.

Read ClientID & TenantID

The ClientID & TenantID can be read out on the start page of the App Registration itself.

image.png

Create client secret

For the ClientSecret you have to switch to the "Certificates & Secrets" tab. A ClientSecret can be added via "New client secret". Then a name can be given there. Attention: Afterwards the ClientSecret is valid for 24 months (2 years) and expires after a certain time. In addition, the value is only displayed once. Save the ClientSecret as the first step in your password storage solution and note the expirationdate.

image.png

Grant permissions

As soon as the authentication via the app registration with the three values works, the permissions have to be assigned. This is a very tricky step, as an app could practically become a global administrator and overwrite or delete any tenant configurations.

The permissions on App Registrations for PowerShell Scripts must always be set to Application-Permissions, because the actions should be executed as App-Context.

Find the required permissions

The best way to find the required permissions is to visit the Microsoft Docs page for Graph API: Microsoft Graph REST API v1.0 endpoint reference - Microsoft Graph v1.0 | Microsoft Learn

image.png

Assign permissions

Once it has been determined which permissions need to be assigned, this can be done in Azure Portal → Microsoft Entra ID → App Registration → API permissions.

First, "Add a permission" must be selected and then Microsoft Graph as an API resource.

image.png

Then it is important that "Application permissions" is selected. There you can then search for the corresponding permission from the Microsoft Docs page and add it using "Add permissions".

image.png

Now that the permission has been added thanks to the previous step, the last thing to do is to check it and then approve it. This can only be done with the "Global Administrator" role, as the app will then receive this permission forever and this is like assigning a role. The person who has Global Administrator must click on "Grand admin consent for <companyname>" in the app so that the permissions also become active.

image.png

Afterwards, the app can be used with the assigned permissions.

Regularly clean up MEID devices with automation

Requirements: Create an App Registration with Directory.ReadWrite.All and Device.ReadWrite.All. For logging you need to have an Azure Function to create an API to centralize logs in an Azure Log Analytics Workspace. 

This automation gets all Microsoft Entra ID Devices via the Microsoft Graph API and checks everyone for their latest sign in. If the login was more than 6 months ago, the device will be deleted from the Microsoft Entra ID. Afterwards, the key figures for the deleted devices and the errors are passed to the Azure Function API, where they are then stored in the corresponding Log Analytics Workspace.

Use case

This automation concept is perfectly suited for large enterprises which don't have an overview of their old Microsoft Entra ID Devices. Authentication in the direction of Microsoft Entra ID is user-independent with an app registration. This makes it perfect for automated and regular execution of this code. In combination with an Azure Runbook that runs weekly, the Microsoft Entra ID environment can be simplified significantly.

PowerShell script

This PowerShell script needs the AzureAD Module, an app registration with the appropriate permissions and an Azure Function to collect the logs and write them to an Azure Log Analytics Workspace.

First install the AzureAD Module.

Install-Module AzureAD

After you have installed the modules and organized the prerequisites (App Registration with Directory.ReadWrite.All and Device.ReadWrite.All permission and an Azure Function API for centralizing the logs) you can fill the four variables to the PowerShell code and run the script.

Import-Module AzureAD

$tenantId= "<yourtenantid>"
$ClientId= "<yourclientidofappregistration>"
$ClientSecret = "<yourclientsecretofappregistration>"
$url="<yoururlofcentralizedlogcollectionfunction>"

$expirationThresholdMonths = 6

Import-Module AzureAD

Function Send-Logs(){

    param (
        [String]$LogType,
        [Hashtable]$LogBodyList
    )
    
    $LogBodyJSON = @"
    {
        "logtype": "$LogType",
        "logbody": {}
    }
"@

    $LogBodyObject = ConvertFrom-JSON $LogBodyJSON
    Foreach($Log in $LogBodyList.GetEnumerator()){
        $LogBodyObject.logbody | Add-Member -MemberType NoteProperty -Name $log.key -Value $log.value
    }
    $Body = ConvertTo-JSON $LogBodyObject
    
    $Response = Invoke-Restmethod -uri $url -Body $Body -Method POST -ContentType "application/json"
    return $Response
}

$Body = @{
"tenant" = $TenantId
"client_id" = $ClientId
"scope" = "https://graph.microsoft.com/.default"
"client_secret" = $ClientSecret
"grant_type" = "client_credentials"
}
$Params = @{
"Uri" = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
"Method" = "Post"
"Body" = $Body
"ContentType" = "application/x-www-form-urlencoded"
}
$AuthResponse = Invoke-RestMethod @Params

$Header = @{
    "Authorization" = "Bearer $($AuthResponse.access_token)"
}


$azurePassword = ConvertTo-SecureString $clientSecret -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential($ClientId , $azurePassword)
Connect-AzAccount -Credential $psCred -TenantId $TenantId -ServicePrincipal

$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$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, "https://graph.windows.net").AccessToken
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id


$Result = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/devices" -Header $Header
$Devices = $Result.value
while ($Result.'@odata.nextLink') {
    $Result = Invoke-RestMethod -Uri $Result.'@odata.nextLink' -Headers $Header
    $Devices += $Result.value
}

$oldDevices = @()
$failedDateDevices = @()
foreach($inactiveDevice in $Devices){
    try{
        $Date = Get-Date($inactiveDevice.approximateLastSignInDateTime)
        if($Date -lt ((Get-Date).AddMonths(-$expirationThresholdMonths))){
            $oldDevices += $inactiveDevice
        }
    } catch {
        $failedDateDevices += $inactiveDevice
    }
}

Write-Warning "Date Errors: $($failedDateDevices.count)"

$failedDeleteDevices = @()
$deletedDevices = @()
foreach($Device in $oldDevices){
    try{
        Remove-AzureADDevice -ObjectId $Device.id
        Write-Output "Deleted: $($Device.displayName)"
        $deletedDevices += $Device
    }catch{
        $failedDeleteDevices += $Device
    }
}
Write-Warning "Delete Errors: $($failedDeleteDevices.count)"

$Logs = @{
    "deletesuccess" = "$($deletedDevices.count)"
    "dateerrors"="$($failedDateDevices.count)"
    "deleteerrors"="$($failedDeleteDevices.count)"
}

Send-Logs -LogType "CleanUpMEIDDevicesExecutions" -LogBodyList $Logs


Regularly clean up MEID guest user with automation

Requirements: Create an App Registration with AuditLog.Read.All and User.ReadWrite.All. For logging you need to have an Azure Function to create an API to centralize logs in an Azure Log Analytics Workspace. 

This automation gets all Microsoft Entra ID Guest User via the Microsoft Graph API and checks everyone for their latest sign in. If the login was more than 6 months ago, the user account will be deleted from the Microsoft Entra ID. Afterwards, the key figures for the deleted devices and the errors are passed to the Azure Function API, where they are then stored in the corresponding Log Analytics Workspace.

Use case

This automation concept is perfectly suited for large enterprises which don't have an overview of all of their Microsoft Entra ID Guests. Authentication in the direction of Microsoft Entra ID is user-independent with an app registration. This makes it perfect for automated and regular execution of this code. In combination with an Azure Runbook that runs weekly, the Microsoft Entra ID environment can be simplified significantly.

PowerShell script

This script has no dependency on any modules and is based on all standard powershell modules. After you organized the prerequisites (App Registration with AuditLog.Read.All and User.ReadWrite.All permission and an Azure Function API for centralizing the logs) you can fill the four variables to the PowerShell code and run the script.

$tenantId= "<yourtenantid>"
$ClientId= "<yourclientidofappregistration>"
$ClientSecret = "<yourclientsecretofappregistration>"
$url="<yoururlofcentralizedlogcollectionfunction>"

$expirationThresholdDays = 180
$NotificateOnDays = @(1,5,10,30)
$SenderUPN = "<yoursenderupn>"
$Subject = "<yourinformationsuccess>"

Function Send-Logs(){

    param (
        [String]$LogType,
        [Hashtable]$LogBodyList
    )
    
    $LogBodyJSON = @"
    {
        "logtype": "$LogType",
        "logbody": {}
    }
"@

    $LogBodyObject = ConvertFrom-JSON $LogBodyJSON
    Foreach($Log in $LogBodyList.GetEnumerator()){
        $LogBodyObject.logbody | Add-Member -MemberType NoteProperty -Name $log.key -Value $log.value
    }
    $Body = ConvertTo-JSON $LogBodyObject
    
    $Response = Invoke-Restmethod -uri $url -Body $Body -Method POST -ContentType "application/json"
    return $Response
}

function Send-Mail {

    param (
        [String]$SenderUPN,
        [Array]$Recipients,
        [Array]$CCRecipients,
        [String]$Subject,
        [String]$Content,
        [String]$DisplayName,
        [Int]$deletionDay,
        [String]$UPN
    )
    
    $Content = @"
    Hello $DisplayName
    <yourmailbody>
"@
  
    $MailBodyJSON = @"
  {
    "message": {
      "subject": "$Subject",
      "body": {
        "contentType": "Text",
        "content": ""
      },
      "toRecipients": [],
      "ccRecipients": []
    },
    "saveToSentItems": "true"
  }
"@

  $MailBodyObject = ConvertFrom-Json $MailBodyJSON
  $MailBodyObject.message.body.content = $Content
  
  Foreach($Recipient in $Recipients){
        $RecipientBodyJson = @"
  {
    "emailAddress": {
        "address": "$Recipient"
    }
  }
"@
        $RecipientBodyObject = ConvertFrom-JSON $RecipientBodyJson
        $MailbodyObject.message.toRecipients += $RecipientBodyObject
    }
  
    Foreach($CCRecipient in $CCRecipients){
        $CCRecipientBodyJson = @"
  {
    "emailAddress": {
        "address": "$CCRecipient"
    }
  }
"@
        $CCRecipientBodyObject = ConvertFrom-JSON $CCRecipientBodyJson
        $MailbodyObject.message.ccRecipients += $CCRecipientBodyObject
    }
  
    $MailoutputbodyJson = ConvertTo-JSON $MailbodyObject -Depth 10

    Write-Host "Sending Mail to $Recipient and $CCRecipient (CC)."
    Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/users/$SenderUPN/sendMail" -Headers $Header -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($MailoutputbodyJson))
}


$Body = @{
"tenant" = $TenantId
"client_id" = $ClientId
"scope" = "https://graph.microsoft.com/.default"
"client_secret" = $ClientSecret
"grant_type" = "client_credentials"
}
$Params = @{
"Uri" = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
"Method" = "Post"
"Body" = $Body
"ContentType" = "application/x-www-form-urlencoded"
}
$AuthResponse = Invoke-RestMethod @Params

$Header = @{
    "Authorization" = "Bearer $($AuthResponse.access_token)"
}

$Result = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/beta/users?`$filter=userType eq 'Guest'&`$select=userPrincipalName,signInActivity,displayName" -Header $Header
$GuestUsers = $Result.value
while ($Result.'@odata.nextLink') {
    $Result = Invoke-RestMethod -Uri $Result.'@odata.nextLink' -Headers $Header
    $GuestUsers += $Result.value
}
$failedDateGuestUsers = @()
$failedDeletedGuestUsers = @()
$failedNotifiedGuestUsers = @()
$successfullynotifiedGuestUsers = @()
$successfullydeletedGuestUsers = @()
foreach($GuestUser in $GuestUsers){
    try{
        $NonInteractiveDayspan = [math]::Round($(New-TimeSpan -Start $(Get-Date $GuestUser.signInActivity.lastNonInteractiveSignInDateTime) -End $(Get-Date)).TotalDays)
        $InteractiveDayspan = [math]::Round($(New-TimeSpan -Start $(Get-Date $GuestUser.signInActivity.lastSignInDateTime) -End $(Get-Date)).TotalDays)
    } catch {
        $failedDateGuestUsers += $GuestUser
    }
    
    if ($NonInteractiveDayspan -lt $InteractiveDayspan) {
        $deletionDay = $expirationThresholdDays - $NonInteractiveDayspan
    } else {
        $deletionDay = $expirationThresholdDays - $InteractiveDayspan 
    }

    if($deletionDay -in $NotificateOnDays -or $deletionDay -lt 0){ # DELETE -or $deletionDay -lt 0
        if($deletionDay -le 0){
            $deletionDay = "paar"
        }
        try{
            $GuestUserMail = (Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$($GuestUser.id)?`$select=mail" -Header $Header).mail
            Send-Mail -Recipients @("$GuestUserMail") -SenderUPN $SenderUPN -DisplayName $GuestUser.displayName -UPN $GuestUser.userPrincipalName -DeletionDay $deletionDay -Subject $Subject
            $successfullynotifiedGuestUsers += $GuestUser
        }
        catch{
            $failedNotifiedGuestUsers += $GuestUser
        }
    }
    if(($deletionDay -lt -5)){
        try{
            $Result = Invoke-RestMethod -Method DELETE -Uri "https://graph.microsoft.com/v1.0/users/$($GuestUser.id)" -Header $Header
            $successfullydeletedGuestUsers += $GuestUser
        }catch{
            $failedDeletedGuestUsers += $GuestUser
        }
    }
}

Write-Warning "Date Errors: $($failedDateUsers.count)"
Write-Warning "Delete Errors: $($failedDeletedGuestUsers.count)"
Write-Warning "Notification Errors: $($failedNotifiedGuestUsers.count)"
Write-Output "Notified GuestUsers: $($successfullynotifiedGuestUsers.count)"
Write-Output "Deleted GuestUsers: $($successfullydeletedGuestUsers.count)"

$Logs = @{
    "deletesuccess"="$($successfullydeletedGuestUsers.count)"
    "notificationsuccess"="$($successfullynotifiedGuestUsers.count)"
    "notificationerrors"="$($failedNotifiedGuestUsers.count)"
    "deleteerrors"="$($failedDeletedGuestUsers.count)"
    "dateerrors"="$($failedDateUsers.count)"
}

Send-Logs -LogType "CleanUpMEIDGuestUserExecutions" -LogBodyList $Logs


Notify license shortage with automation

Requirements: Create an App Registration with LicenseAssignment.ReadWrite.All permissions and a client secret.

With this automation all licenses and their usage are evaluated via Graph API. A message is posted in a team channel when a certain amount of usage of a license is reached.

Use case

This automation helps large organizations with many Microsoft 365 licenses to keep track. If this automation is carried out regularly, interruptions in operation and overbooked licenses can be noticed at an early stage. To run such scripts on a regular basis, an Azure Runbook is a good choice.

Teams webhook notification

To send a notification to a Teams Channel you can use a webhook. To create a webhook for a channel you can rightclick on a channel and then choose "Connectors".

image.png

There you can then configure a new incoming webhook:

image.png

You have to name the webhook first. This name will be displayed as the sender of the message. You can upload an image which will then be used as a profile picture of the sender. After that you can click "create". This will return an URL which then can be used to send a payload message. This will then look like that:

image.png

PowerShell script

This PowerShell script needs an App Registration and the according LicenseAssignment.ReadWrite.All Graph API permission. This script uses the three Azure Automation variables "$tenantid", "$clientid" and "$clientsecret" as well as the perviously created "$WebhookURI", which should be added before.

$tenantId=Get-AutomationVariable -Name "<nameoftenantidrunbookvariable>"
$ClientId=Get-AutomationVariable -Name "<nameofclientidrunbookvariable>"
$CredentialObject=Get-AutomationPSCredential -Name '<nameofclientsecretrunbookcredentials>'
$ClientSecret = $CredentialObject.GetNetworkcredential().password

$WebhookURI = ""
$PercentageAlert = "98"

$Body = @{
"tenant" = $TenantId
"client_id" = $ClientId
"scope" = "https://graph.microsoft.com/.default"
"client_secret" = $ClientSecret
"grant_type" = "client_credentials"
}

$Params = @{
"Uri" = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
"Method" = "Post"
"Body" = $Body
"ContentType" = "application/x-www-form-urlencoded"
}

$AuthResponse = Invoke-RestMethod @Params

$Header = @{
    "Authorization" = "Bearer $($AuthResponse.access_token)"
}

$AllLicenses = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/subscribedSkus" -Header $Header

Foreach($License in $AllLicenses.value){
    if($License.prepaidUnits.enabled -ge 50){
        try{
            $LicensePercentage = ($License.consumedUnits/$License.prepaidUnits.enabled*100)
        }
        catch{
            $null
        }
        if($LicensePercentage -ge $PercentageAlert){
            $CurrentTime = Get-Date
$JsonBody = @"
{
    "@context": "https://schema.org/extensions",
    "@type": "MessageCard",
    "themeColor": "880808",
    "title": "License warning: $($License.skuPartNumber)",
    "text": "License has more than 98% allocations. Please order new licenses in order not to jeopardize the operation.<br><br>Licensename: $($License.skuPartNumber) <br><br>Licenses available: $($License.prepaidUnits.enabled - $License.consumedUnits) <br><br> Licenses total: $($License.prepaidUnits.enabled) <br> Licenses assigned: $($License.consumedUnits) <br><br> Time of the evaluation: $($CurrentTime.addHours(2))<br><br> More details about the license: https://admin.microsoft.com/#/licensedetailpage/$($License.skuId)",
  }
"@

            Invoke-RestMethod -Method Post -Body $JsonBody -Uri $WebhookURI -Header @{"content-type" = "application/json; charset=UTF-8"}
        }
    }
}

Notify client app secret expiration with automation

Requirements: Create an App Registration with Application.Read.All permissions and a client secret.

With this automation all client secrets and their expiration dates are evaluated via Graph API. A message is posted in a team channel when a client secret is about to expire.

Use case

This automation helps large organizations with many Microsoft Entra ID App Registrations to keep track of expiration dates of their Client Secrets. If this automation is carried out regularly, interruptions in operation and expired client secrets can be noticed at an early stage. To run such scripts on a regular basis, an Azure Runbook is a good choice.

Teams webhook notification

To send a notification to a Teams Channel you can use a webhook. To create a webhook for a channel you can rightclick on a channel and then choose "Connectors".

image.png

There you can then configure a new incoming webhook:

image.png

You have to name the webhook first. This name will be displayed as the sender of the message. You can upload an image which will then be used as a profile picture of the sender. After that you can click "create". This will return an URL which then can be used to send a payload message.

PowerShell script

This PowerShell script needs an App Registration and the according Application.Read.All Graph API permission. This script uses the three Azure Automation variables "$tenantid", "$clientid" and "$clientsecret" as well as the perviously created "$WebhookURI", which should be added before.

$tenantId=Get-AutomationVariable -Name "<nameoftenantidrunbookvariable>"
$ClientId=Get-AutomationVariable -Name "<nameofclientidrunbookvariable>"
$CredentialObject=Get-AutomationPSCredential -Name '<nameofclientsecretrunbookcredentials>'
$ClientSecret = $CredentialObject.GetNetworkcredential().password

$WebhookURI = "<yourwebhookuri>"

$NotifyOnDays = @(1,2,3,4,5,10,15,20,30)
$DeleteSecretIfExpiredForDays = 30

$Body = @{
"tenant" = $TenantId
"client_id" = $ClientId
"scope" = "https://graph.microsoft.com/.default"
"client_secret" = $ClientSecret
"grant_type" = "client_credentials"
}

$Params = @{
"Uri" = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
"Method" = "Post"
"Body" = $Body
"ContentType" = "application/x-www-form-urlencoded"
}

$AuthResponse = Invoke-RestMethod @Params

$Header = @{
    "Authorization" = "Bearer $($AuthResponse.access_token)"
}

function Send-ToTeams {
    Param(
        $SecretOB
    )

    $Body = @{
"@context" = "https://schema.org/extensions"
"@type" = "MessageCard"
"themeColor" = "880808"
"title" = "Secret of App Registration $($SecretOB.AppName) expires in $($SecretOB.ExpiresInXDays)!"
    "text" = "<ul><li>App: $($SecretOB.AppName)</li><li>AppId: $($SecretOB.AppID)</li><li>SecretId: $($SecretOB.SecretID)</li><li>Expirationdate: $($SecretOB.ExpiryDate) (in $($SecretOB.ExpiresInXDays) days)</li></ul>"
}

    $JsonBody = $Body | ConvertTo-JSON

    Invoke-RestMethod -Method Post -Body $JsonBody -Uri $WebhookURI -Headers @{"content-type" = "application/json; charset=UTF-8"} | out-null
}


$AllApps = @()

$Response = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/applications" -Headers $Header
$AllApps = $Response.value
 
while ($Response.'@odata.nextLink') {
    $Response = Invoke-RestMethod -Method Get -Uri $Response.'@odata.nextLink' -Headers $Header
    $AllApps += $Response.value
}
 
Write-Output "$($Allapps.Count) Apps were discovered"


$AllSecrets = @()

foreach ($App in $AllApps) {
    foreach ($AppSecret in $App.passwordCredentials) {
        $ExpiresInXDays = $(New-TimeSpan -Start $(Get-Date) -End $(Get-Date $AppSecret.endDateTime)).TotalDays
        switch ($ExpiresInXDays) {
            { $_ -lt 30 } {$Status = "Expires Soon" }
            { $_ -lt 0 } {$Status = "Expired" }
            Default {$Status = "Ok" }
        }

        $SecretOB = [PSCustomObject]@{
            AppID = $App.AppID
            AppName = $App.DisplayName
            SecretID = $AppSecret.KeyID
            ExpiryDate = $AppSecret.endDateTime
            ExpiresInXDays = [math]::Round($ExpiresInXDays)
            Status = $Status
        }

        $AllSecrets += $SecretOB

        if ($SecretOB.ExpiresInXDays -in $NotifyOnDays) {
            [array]$AppOwners = $(Invoke-RestMethod -Method get -Uri "https://graph.microsoft.com/v1.0/applications/$($App.ID)/owners" -Headers $Header).value

            Write-Warning "Secret $($SecretOB.SecretID) on App $($SecretOB.AppID) expires in $($SecretOB.ExpiresInXDays) days."

            if ($AppOwners) {
                foreach ($AdminOwner in $AppOwners | Where-Object { $_.userPrincipalName -like "A_" }) {
                    $SAM = $AdminOwner.userPrincipalName.Split("@")[0].replace("A_","")
                    $RegularUser = $(Invoke-RestMethod -Method GET -uri "https://graph.microsoft.com/v1.0/users?`$filter=startswith(MailNickName, '$SAM')" -Headers $Header).Value
    
                    $AppOwners += $RegularUser
                }
                if ([array]$AppOwners.Mail) {
                    Send-ToTeams -SecretOB $SecretOB
                } else {
                    Send-ToTeams -SecretOB $SecretOB
                }
            } else {
                Send-ToTeams -SecretOB $SecretOB
            }
        } elseif ($SecretOB.ExpiresInXDays -lt -$DeleteSecretIfExpiredForDays) {
            if ($($App.passwordCredentials | Where-Object { $_.endDateTime -gt $(get-date) }).Count -ge 1) {
                Write-Output "Secret $($SecretOB.SecretID) on App $($SecretOB.AppID) expired $(-$SecretOB.ExpiresInXDays) Days ago and can be deleted. Client has at least one Secret that does not expire whithin the next $DeleteSecretIfExpiredForDays days."
            } else {
                Write-Output "Secret $($SecretOB.SecretID) on App $($SecretOB.AppID) expired $(-$SecretOB.ExpiresInXDays) Days ago and can be deleted"
            }

            $SecretDeletionBody = @"
            {
                "keyId": "$($SecretOB.SecretID)"
            }
"@
            #Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/applications/$($SecretOB.AppID)/removePassword" -Body $SecretDeletionBody -Headers $Header
        }
    }
}

If you want to automatically delete old app registrations that have expired for more than a defined number of days ($DeleteSecretIfExpiredForDays) and no longer have any active secrets, you can show the time 118. This will permanently delete all app registrations that have expired client secrets.

Get App Registrations by User Principal Name

Graph API permission "Application.Read.All" or Cloud Application Administrator Role.

This guide explains how you can get all App Registrations by a certain user. This can be handy when someone leaves a company and it needs to be evaluated which app registration was maintained by that person.

Preparations

For this script to work you need the permission to list the App Registrations. This needs the Application.Read.All permission. The access token can be acquired either by delegated or application permissions.

This is a manual for application based token acquisition: Create application acc... | LNC DOCS (lucanoahcaprez.ch)
And this one is for user based tokens: Create user access tok... | LNC DOCS (lucanoahcaprez.ch)

Evaluate App Registration Owners

This is the script that evaluates the owners of the app registrations and the app registrations of the corresponding user are stored in the $UsersApplication variable. In advance, the UPN of the user to be evaluated must be stored in the $UserPrincipleName variable.

$AccessToken = "<yourazureadaccesstoken>"
$UserPrincipalName = "<userprincipalnametosearchfor>"

$Header = @{
    "Authorization" = "Bearer $($AccessToken)"
}

$Params = @{
    "Method"      = "Get"
    "Uri"         = "https://graph.microsoft.com/v1.0/applications"
    "Headers"     = $Header
    "ContentType" = "application/json"
}

$Result = Invoke-RestMethod @Params
$AllApplications = $Result.value
while ($Result.'@odata.nextLink') {
    $Result = Invoke-RestMethod -Uri $Result.'@odata.nextLink' -Headers $Header
    $AllApplications += $Result.value
}

$UsersApplication = @()

Foreach($Application in $AllApplications){
    $Params.Uri = "https://graph.microsoft.com/v1.0/applications/$($Application.id)/owners?`$select=id,userPrincipalName"
    $ApplicationInfo = (Invoke-RestMethod @Params).value
    if($ApplicationInfo.userPrincipalName -eq $UserPrincipalName){# -and $ApplicationInfo.userPrincipalName.count -eq 1){ #This can be displayed if you want to serach only for apps where the user is the only owner
        $UsersApplication += $Application
    }
}

Change MFA Phone via Graph API

This automation sets the primary mobile number as MFA method according to a UPN. This can be used, for example, if from an internal store or user interface (e.g. ServiceNow) the users should automatically set the MFA method a first time. For example, this code can be placed in an Azure Runbook or Azure Function and executed via a trigger.

Requirements

To execute this code you need to create an App Registration and add the Permissions "UserAuthenticationMethod.ReadWrite.All". How you can create an App Registration and how you get the variables "TenantId", "ClientId" and "ClientSecret" with the according values, you can view this manual: Get app details and gr... | LNC DOCS (lucanoahcaprez.ch)

PowerShell Code

This code must be filled with the correct variables for everything to work. On the one hand it needs the standard variables for the Graph Authentication: "$TenantID", "$ClientID", "$Clientsecret". And on the other hand the variables "$Email" and "$PhoneNumber" are used to locate the user to whom the mobile number should be set as MFA method.

$Email = "<youremail>"
$PhoneNumber = "<yourphonenumber>"

# Filter empty spaces
if($PhoneNumber.contains(" ")){
	$PhoneNumber = $PhoneNumber.replace(" ","")
}

$TenantId = "<yourtenantid>"
$ClientId = "<yourappregistrationid>"
$ClientSecret = "<yourclientsecret>"

$Body = @{
"tenant" = $TenantId
"client_id" = $ClientId
"scope" = "https://graph.microsoft.com/.default"
"client_secret" = $ClientSecret
"grant_type" = "client_credentials"
}

$Params = @{
"Uri" = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
"Method" = "Post"
"Body" = $Body
"ContentType" = "application/x-www-form-urlencoded"
}

$AuthResponse = Invoke-RestMethod @Params

$Headers = @{
    "Authorization" = "Bearer $($AuthResponse.access_token)"
}


# Get User ID By UPN
$UsersResponse = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$email" -ContentType "Application/Json" -Headers $Headers
$UserId = $UsersResponse.id

# Change Phone Number for MFA

$PhoneMethod = @"
{
    "phoneNumber":"$PhoneNumber",
    "phoneType":"mobile"
}
"@
$MFAResponse = Invoke-RestMethod -Method PUT -Uri "https://graph.microsoft.com/beta/users/$UserId/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7" -ContentType "Application/Json" -Body $PhoneMethod -Headers $Headers

Start-Sleep 30

# Compare Phone Numbers

$MFAMethod = Invoke-RestMethod -Method GET -Uri "https://graph.microsoft.com/beta/users/$UserId/authentication/phoneMethods" -ContentType "Application/Json" -Headers $Headers

$AzurePhoneNumber = $MFAMethod.value.phoneNumber.Replace(" ","")

if($AzurePhoneNumber -eq $PhoneNumber){
    Write-Output "success"
}else{
    Write-Output "Failed to compare Azure Phone Number to Input from SNOW."
}

Move Azure Subscriptions between Microsoft Entra ID tenants

Prerequisites: Permissions to invite new guest users in source tenant. Permissions to grant Owner permissions to subscription in source tenant. Permissions to accept guest invitations in destination tenant.

This guide shows you how to move an Azure subscription from one Microsoft Entra ID tenant to another. This can be useful if business requirements or other company structures have changed and you do not want to rebuild the resources.

Instructions using Entra ID guest user

The procedure is quite simple. The source tenant must simply invite the administrator account on the destination tenant as a guest. Once the invitation has been accepted, the user from the destination tenant can see the subscription in the Azure subscription and transfer it to their destination tenant via the Azure portal.

Limitations

There are various limitations. Here is a list of some that are already known in the community. It certainly makes sense to search the official Microsoft pages here.

This list is not exhaustive and migrating subscriptions is always associated with risk.

How it is done

First you have to invite the user account from the destination tenant as guest user in the source tenant.

Log in to the source tenant and make sure that you have all permissions to invite guest users. You must also be able to adjust the IAM permissions on the subscription that you want to migrate.

Invite new guest user

You can now invite the user account in the source tenant as described in this guide: Quickstart: Add a guest user and send an invitation - Microsoft Entra External ID | Microsoft Learn

Make sure that the user has accepted the invitation. Check the guest users state under "Invitation state":

image.png

Add Azure role based access to guest user

If the user is successfully registered in the Entra ID, the subscription can be opened in the Azure portal and a new role assignment can be made under Access control.

image.png

Make sure to add the correct privileged administrator role:

image.png

Make sure to select the corresponding user account of the destination tenant. Under "Conditions" select the second property to grant all admin privileges:

image.png

Then you can create the role assignment.

Switch account and make sure to use the invited user account of the destination tenant from now on.

Start subscription migration

Switch directory to the source tenant and do the following steps:

Go to subscriptions and there you should see the subscription of the source tenant that you want to move. Open the subscription and make sure you are in the Overview blade.

Now you can choose "Change directory":

image.png

You can then select the destination tenant in this dialog. You must confirm that RBAC roles cannot be transferred.

image.png

Wait for the confirmation message that the subscription is being migrated. It can then take up to 10 minutes before you can reuse all resources. However, the resources are not offline, they are just not displayed. There is normally no downtime with this type of migration.

image.png

Switch to the destination tenant and wait for the completion of the migration. Make sure once the migration is complete, make sure that everything works as expected.

Migration of billing ownership

Once the previous steps have been completed, you can do the following. The billing accounts for the subscription are still in the old tenant at this stage and will remain there unless a migration is carried out.
If this migration is to be carried out, we recommend working through these instructions:

Change the account back to the source tenant.

image.png

image.png

If the user does not have a mailbox (avoid unnecessary licenses), this button can be clicked again after execution to view the link for the subscription transfer and the status of the process.

image.png

Change the account to the destination tenant.

You must then log in to the link with the destination tenant admin and enter the company's contact details including the new credit card. Once this has been completed, you will be redirected to a page where you will see the billing account that has been transferred to the destination tenant.

Microsoft Docs: Transfer billing ownership of an MOSP Azure subscription - Microsoft Cost Management | Microsoft Learn

Single sign on (SSO) with third party applications

Microsoft Entra ID is the authentication solution for Microsoft Cloud products. Using various standards such as OAuth 2.0 or SAML, Microsoft Entra ID can be used as a central management platform for third party solutions, whereby granular access to apps, groups, authorizations and much more can be set. This allows external applications without their own user administration to offer a secure and nice user experience. Unfortunately, the integration of these standards is quite different for each application and therefore sometimes requires in-depth knowledge of the application environment.

Single sign on (SSO) with third party applications

Microsoft Entra ID SSO for Grafana

Prerequisites: Ability to create an app registration with delegated standard rights and access to the Grafana Docker volume. Grafana should be installed and administrator access to the web interface should be available.

These instructions describe how a Grafana Docker instance can be equipped with all the advantages of Single Sign On (SSO) using Microsoft Entra ID. Since there are different types of SSO, it is important to know that Grafana has many possibilities and offers granular, requirement-specific implementation options. For example, this means that automatic sign-up, role and user mapping, authentication scope and much more can be set.

Create App Registration

First, an app registration including client secret must be created in Microsoft Entra ID. All settings can be left at the default values. Important settings are the Redirect URIs under the Authentication tab. Set these URIs to your external or internal domain on which Grafana is available. These URIs will be used for Microsoft Entra ID to know where to redirect the user in case of successful logins.

image.png

Add the corresponding permissions for OpenID Connect as delegated permissions and grant admin consent for your tenant.

image.png

Create a client secret for the application and save the tenant ID, application ID and client secret in your password manager. You can find instructions for this information here: Get app details and grant permissions to app registration

Enable authentication login provider

To equip Grafana with SSO options, the SSO API must be activated in the configuration file. To do this, the following file “/etc/grafana/grafana.ini” must be adapted. This file is normally saved in a persistent location. Create the document if it does not already exist and add the following configuration line:

[feature_toggles]
ssoSettingsApi = true

You should then restart the Docker container or the application.

Check whether configuration was successful

If everything is set up correctly, you can log in with the administrator account in the webgui of your Grafana installation.

You should then be able to see the OAuth providers supported by Grafana in the “Authentication” tab. 

image.png

Setup Microsoft Entra ID provider

This step requires the authentication details TenantID, ClientID, Client Secret from the first step.

In the Grafana web interface select "Authentication" -> "Azure AD". There you can enter the credentials from Microsoft Entra ID. Enter the information as described here:

After these settings are properly configured your users should be able to sign into Grafana. For more granular settings and things like role mapping or default Grafana groups, change the settings under "User mapping" and "Extra security measures".

image.png

Single sign on (SSO) with third party applications

Microsoft Entra ID SSO for Proxmox

Prerequisites: Ability to create an app registration with delegated standard rights. Proxmox should be installed and access to the Datacenters Realms section should be possible.

Proxmox allows various external authentication services via protocols such as Active Directory, LDAP or OpenID Connect. We will use the latter for the Microsoft Entra ID connection and SSO functionality.

Limitations

Proxmox allows the automatic creation of user objects, but is otherwise relatively limited compared to other applications, as it does not use the OAUTH 2.0 standard but only handles logins via Open ID Connect. These certain limitations must be taken into account when introducing this setup.

In addition, logins will only be possible for the Webgui. The login on the individual cluster nodes is still regulated via the Linux authentication of the individual hosts. This means that no console connections can be made to the host shells with the Microsoft Entra ID user objects.

Create App Registration

First, an app registration including client secret must be created in Microsoft Entra ID. All settings can be left at the default values. Important settings are the Redirect URIs under the Authentication tab. Set these URIs to your external or internal domain on which Proxmox is available. These URIs will be used for Microsoft Entra ID to know where to redirect the user in case of successful logins.

image.png

Add the corresponding permissions for OpenID Connect as delegated permissions and grant admin consent for your tenant.

image.png

Create a client secret for the application and save the tenant ID, application ID and client secret in your password manager. You can find instructions for this information here: Get app details and grant permissions to app registration

Setup Microsoft Entra ID in Proxmox Realm

This step requires the authentication details TenantID, ClientID, Client Secret from the first step.

In the Proxmox web interface select "Datacenter" -> "Realms" -> "Add" -> "OpenID Connect Server". There you can enter the credentials from Microsoft Entra ID. Enter the information as described here:image.png

After these settings are properly configured your users should be able to sign into Proxmox web interface. After sign in the default grouping, role and permissions mechanisms from Proxmox take place.

image.png

Single sign on (SSO) with third party applications

Microsoft Entra ID SSO for Portainer

Prerequisites: Ability to create an app registration with delegated standard rights. Portainer should be installed and administrator access to the web interface should be available.

This guide will take you through the various stages of installing SSO using Microsoft Entra ID. Unfortunately, it is not possible to add the SSO functions in the Portainer Community Edition (CE). Accordingly, we have to purchase a free license of the Business Edition (BE) and upgrade the Portainer instance. You will then be able to manage the logins and authorizations for the web interface via Microsoft Entra ID. Functions such as automatic user provisioning or default permissions are also supported by Portainer.

Create App Registration

First, an app registration including client secret must be created in Microsoft Entra ID. All settings can be left at the default values. Important settings are the Redirect URIs under the Authentication tab. Set these URIs to your external or internal domain on which Portainer is available. These URIs will be used for Microsoft Entra ID to know where to redirect the user in case of successful logins.

image.png

Add the corresponding permissions for OpenID Connect as delegated permissions and grant admin consent for your tenant.

image.png

Create a client secret for the application and save the tenant ID, application ID and client secret in your password manager. You can find instructions for this information here: Get app details and grant permissions to app registration

Acquire Portainer License

To activate the SSO functionality, the Portainer Comunity Edition must be replaced by a Business Edition. Don't worry, it costs nothing. At least for a Homelab environment and installation with less than 3 environments. For business customer licensing in Portainer Business Edition is based on the number of nodes you are managing.

Create an account and follow the instructions on this page: Take 3 - Get your first 3 nodes free (portainer.io)

Upgrade Portainer instance

After you have received the license key via email you can start upgrading your Portainer CE to a Portainer BE instance. Make sure to create a backup before you upgrade the instance.

When running portainer inside a single docker container, it is no simpler than changing the image from "portainer/portainer-ce" to "portainer/portainer-ee" and restart the stack or containers. You can find more in depth guides and version specific upgrade manuals here: Docker Standalone | Portainer Documentation

Check activation status

After installing the license, this can be checked in the web interface. To do this, navigate to “Licenses” and check whether your license is installed and the limitation to 3 nodes is displayed. Then you are ready to add external authentication providers such as Microsoft Entra ID. Go to the next step.

Setup Microsoft Entra ID login provider

For the setup, log into the web interface with an administrator account. You can then select the “OAuth” option under Settings -> Authentication -> Authentication method. The following settings enable automatic user provision or a default group. Configure this as it suits you. Attention: It is recommended that the option “hide internal authentication prompt” is not activated so that the values can still be adjusted in the event of a misconfiguration of the OAuth provider settings. If this is activated, you can lock yourself out and have to rebuild the Portainer instance. 

image.png

You can then select the "Microsoft OAuth provider" under “Provider” and fill in the TenantID, ClientID and Client Secret options with the corresponding values from the app registration. After saving the settings, you can control who can connect to the web interface of the Portainer instance via the app registration members.

image.png

Single sign on (SSO) with third party applications

Microsoft Entra ID SSO for Mealie

Prerequisites: Ability to create an app registration with delegated rights and access to the Mealie Docker volume or startup method. Mealie should be installed correctly.

Mealie makes recipe management and planning extremely easy. The Mealie software offers an OpenID interface, which means that Microsoft Entra ID can easily be used as an identity provider and permission manager.

Create App Registration

First, an app registration including client secret must be created in Microsoft Entra ID. All settings can be left at the default values. Important settings are the Redirect URIs under the Authentication tab. Set these URIs to your external or internal domain on which Mealie is available. These URIs will be used for Microsoft Entra ID to know where to redirect the user in case of successful logins.

image.png

Add the corresponding permissions for OpenID Connect as delegated permissions and grant admin consent for your tenant.

image.png

Create a client secret for the application and save the tenant ID, application ID and client secret in your password manager. You can find instructions for this information here: Get app details and grant permissions to app registration

Setup Microsoft Entra ID login provider

With Mealie, the Microsoft Entra ID configurations can be set up using environment variables. Installation using Docker is mandatory for these instructions. The following environment variables enable the configuration of the OpenID integration. You can find more information on all of Mealie's environment variables here: Backend Configuration - Mealie

The relevant environment variables for OpenID are as follows:

Side note: As this type of configuration involves environment variables, these contents can also be transferred via the volume as an .env file, specified with a simple startup command or specified in another declarative context (Kubernetes manifest, Terraform, etc.).

Docker compose example

This Docker Compose file shows a possible configuration for Mealie that authenticates using Microsoft Entra ID. In addition, a mail server is also specified for outgoing SMTP mail traffic.

Customize this content with your specifications and save the content in a normal docker-compose.yaml file. As this is Docker Compose, the application can be started easily with the following command (in detach mode -> -d):

docker compose up -d

version: "3.7"
services:
  mealie:
    image: ghcr.io/mealie-recipes/mealie:latest
    container_name: <yourcontainername>
    ports:
        - "8600:<yourpublicport>"
    volumes:
      - <yourpersistentpath>:/app/data/
    environment:
      - ALLOW_SIGNUP=true
      - OIDC_AUTH_ENABLED=true
      - OIDC_SIGNUP_ENABLED=true
      - OIDC_CONFIGURATION_URL=https://login.microsoftonline.com/<yourtenantid>/v2.0/.well-known/openid-configuration
      - OIDC_CLIENT_ID=<yourclientid>
      - OIDC_CLIENT_SECRET=<yourclientsecret>
      - OIDC_PROVIDER_NAME=Microsoft Entra ID
      - PUID=1000
      - PGID=1000
      - TZ=<yourtimezone>
      - MAX_WORKERS=1
      - WEB_CONCURRENCY=1
      - BASE_URL=https://<yourmealiedomain>
      - SMTP_HOST=<yoursmtpmailhost>
      - SMTP_PORT=587
      - SMTP_FROM_EMAIL=<yoursmtpmail>
      - SMTP_USER=<yoursmtpmailuser>
      - SMTP_PASSWORD=<yoursmtpmailpassword>
      - SMTP_FROM_NAME=<yourmailname>
    restart: unless-stopped

Single sign on (SSO) with third party applications

Microsoft Entra ID SSO for Bookstack

Prerequisites: Ability to create an app registration with delegated rights and access to the Bookstack Docker volume or startup method. Bookstack should be installed correctly.

Bookstack offers an OpenID interface, which means that Microsoft Entra ID can easily be used as an identity provider for managing access and permission within Bookstack. The functionalities are more limited than other integrations. However, simple functionalities such as automatic user creation and email verification can be customized.
This guide is a compilation of the main documentation of Bookstack: Third Party Authentication · BookStack (bookstackapp.com)

Create App Registration

First, an app registration including client secret must be created in Microsoft Entra ID. All settings can be left at the default values. Important settings are the Redirect URIs under the Authentication tab. Set these URIs to your external or internal domain on which Bookstack is available. These URIs will be used for Microsoft Entra ID to know where to redirect the user in case of successful logins.

image.png

Add the corresponding permissions for OpenID Connect as delegated permissions and grant admin consent for your tenant.

image.png

Create a client secret for the application and save the tenant ID, application ID and client secret in your password manager. You can find instructions for this information here: Get app details and grant permissions to app registration

Setup Microsoft Entra ID login provider

With Bookstack, the Microsoft Entra ID configurations can be set up using environment variables. Installation using Docker is mandatory for these instructions. The following environment variables enable the configuration of the OpenID integration.

The relevant environment variables for OpenID are as follows:

Side note: As this type of configuration involves environment variables, these contents can also be transferred via the volume as an .env file, specified with a simple startup command or specified in another declarative context (Kubernetes manifest, Terraform, etc.).

Docker compose example

This Docker Compose file shows a possible configuration for Bookstack that authenticates using Microsoft Entra ID. In addition, the database container and mail settings are also specified.

Customize this content with your specifications and save the content in a normal docker-compose.yaml file. As this is Docker Compose, the application can be started easily with the following command (in detach mode -> -d):

docker compose up -d

version: "3"
services:
  <yourbookstackcontainername>:
    image: lscr.io/linuxserver/bookstack
    container_name: <yourbookstackcontainername>
    environment:
      - PUID=1000
      - PGID=1000
      - APP_URL=https://<yourbookstackdomain>
      - DB_HOST=<yourmariadbcontainername>
      - DB_USER=<yourdbuser>
      - DB_PASS=<yourdbpassword>
      - DB_DATABASE=<yourdbname>
      - MAIL_HOST=<yourmailserver>
      - MAIL_PORT=587
      - MAIL_FROM_NAME=<yourmailname>
      - MAIL_FROM=<yoursmtpmail>
      - MAIL_USERNAME=<yoursmtpmailuser>
      - MAIL_PASSWORD=<yoursmtpmailpassword>
      - AZURE_APP_ID=<yourclientid>
      - AZURE_APP_SECRET=<yourclientsecret>
      - AZURE_TENANT=<yourtenantid>
      - AZURE_AUTO_REGISTER=true
      - AZURE_AUTO_CONFIRM_EMAIL=true
    volumes:
      - <yourpersistentpathforbookstack>:/config
    ports:
      - 6875:80
    restart: unless-stopped
    depends_on:
      - <yourmariadbcontainername>
  <yourmariadbcontainername>:
    image: lscr.io/linuxserver/mariadb
    container_name: <yourmariadbcontainername>
    environment:
      - PUID=1000
      - PGID=1000
      - MYSQL_ROOT_PASSWORD=<yourdbrootpassword>
      - TZ=<yourtimezone>
      - MYSQL_DATABASE=<yourdbname>
      - MYSQL_USER=<yourdbuser>
      - MYSQL_PASSWORD=<yourdbpassword>
    volumes:
      - <yourpersistentpathformariadb>:/config
    restart: unless-stopped

Single sign on (SSO) with third party applications

Microsoft Entra ID SSO for Gitlab

Prerequisites: Ability to create an app registration with delegated standard rights and access to the Gitlab Docker volume. Gitlab should be installed and administrator access to the web interface should be available.

This guide describes how a Gitlab Docker instance can be equipped with all the benefits of Single Sign On (SSO) using Microsoft Entra ID. Gitlab offers options to make granular settings for the login behaviour.

Things to consider & limitations

In order to be able to log in to the GIT CLI using OAuth, it is essential to work through the following instructions. Since your own Gitlab instance does not yet have an application with which the CLI can authenticate itself. After you have completed this setup, you must note the following: OAuth SSO in CLI using... | LNC DOCS (lucanoahcaprez.ch)

Create App Registration

First, an app registration including client secret must be created in Microsoft Entra ID. All settings can be left at the default values. Important settings are the Redirect URIs under the Authentication tab. Set these URIs to your external or internal domain on which Gitlab is available. These URIs will be used for Microsoft Entra ID to know where to redirect the user in case of successful logins.

image.png

Add the corresponding permissions for OpenID Connect as delegated permissions and grant admin consent for your tenant.

image.png

Create a client secret for the application and save the tenant ID, application ID and client secret in your password manager. You can find instructions for this information here: Get app details and grant permissions to app registration

Setup Microsoft Entra ID login provider

This step requires the authentication details TenantID, ClientID, Client Secret from the first step.

With Gitlab, the Microsoft Entra ID configurations can be set up using the GITLAB_OMNIBUS_CONFIG environment variable. The following part of the variable enables the configuration of the OpenID integration. 

The relevant environment variables for OpenID are as follows:

Side note: As this type of configuration involves the omnibus variable, these contents can also be transferred via the volume as the gitlab.rb file, specified with a simple startup command or specified in another declarative context (Kubernetes manifest, Terraform, etc.).

Docker compose example

This Docker Compose file shows a possible configuration for Gitlab that authenticates using Microsoft Entra ID. In addition mail settings are also specified.

Customize this content with your specifications and save the content in a normal docker-compose.yaml file. As this is Docker Compose, the application can be started easily with the following command (in detach mode -> -d):

docker compose up -d
version: '3.6'
services:
  web:
    container_name: <yourcontainername>
    image: 'gitlab/gitlab-ee:latest'
    restart: unless-stopped
    hostname: '<yourgitlabdomain>'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'https://<yourgitlabdomain>'
        gitlab_rails['gitlab_shell_ssh_port'] = <yoursshshellport>
        gitlab_rails['smtp_enable'] = true
        gitlab_rails['smtp_address'] = "<yourmailserver>"
        gitlab_rails['smtp_port'] = 587
        gitlab_rails['smtp_user_name'] = "<yoursmtpmailuser>"
        gitlab_rails['smtp_password'] = "<yoursmtpmailpassword>/"
        gitlab_rails['smtp_domain'] = "<yourgitlabdomain>"
        gitlab_rails['smtp_authentication'] = "login"
        gitlab_rails['smtp_enable_starttls_auto'] = true
        gitlab_rails['omniauth_enabled'] = true
        gitlab_rails['omniauth_allow_single_sign_on'] = ['azure_activedirectory_v2']
        gitlab_rails['omniauth_block_auto_created_users'] = false
        gitlab_rails['omniauth_providers'] = [
          {
            "name" => "azure_activedirectory_v2",
            "label" => "Microsoft Entra ID",
            "args" => {
              "client_id" => "<yourclientid>",
              "client_secret" => "<yourclientsecret>",
              "tenant_id" => "<yourtenantid>",
            }
          }
        ]
    ports:
      - '<yourhttpport>:80'
      - '<yourhttpsport>:443'
      - '<yoursshport>:22'
    volumes:
      - <yourpersistentpath>/config:/etc/gitlab
      - <yourpersistentpath>/logs:/var/log/gitlab
      - <yourpersistentpath>/data:/var/opt/gitlab
    shm_size: '256m'