Skip to main content

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 check 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>"

$expirationThresholdMonths = 6

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)"
}

$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
}

$oldGuestUsers = @()
$failedDateUsers = @()
foreach($GuestUser in $GuestUsers){
    try{
        $NonInteractiveDate = Get-Date($GuestUser.signInActivity.lastNonInteractiveSignInDateTime)
        $InteractiveDate = Get-Date($GuestUser.signInActivity.lastSignInDateTime)
        if($NonInteractiveDate -lt ((Get-Date).AddMonths(-$expirationThresholdMonths)) -and ($InteractiveDate -lt ((Get-Date).AddMonths(-$expirationThresholdMonths)))){
            $oldGuestUsers += $GuestUser
        }
    } catch {
        $failedDateUsers += $GuestUser
    }
}
Write-Warning "Date Errors: $($failedDateUsers.count)"

$failedDeletedGuestUsers = @()
$deletedGuestUsers = @()
foreach($GuestUser in $oldGuestUsers){
    try{
        $Result = Invoke-RestMethod -Method DELETE -Uri "https://graph.microsoft.com/v1.0/users/$($GuestUser.id)" -Header $Header
        $deletedGuestUsers += $GuestUser
    }catch{
        $failedDeletedGuestUsers += $GuestUser
    }
}
Write-Warning "Delete Errors: $($failedDeletedGuestUsers.count)"

$Logs = @{
    "deletesuccess"="$($deletedGuestUsers.count)"
    "deleteerrors"="$($failedDeletedGuestUsers.count)"
    "dateerrors"="$($failedDateUsers.count)"
}

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