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 getgets all Microsoft Entra ID DevicesGuest User via the Microsoft Graph API and check everyone for their latest sign in. If the login was more than 6 months ago, the deviceuser 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 old Microsoft Entra ID Devices.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 PowerShell script needshas theno AzureADdependency Module,on 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 theany 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.

Import-Module AzureAD

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

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"beta/users?`$filter=userType eq 'Guest'&`$select=userPrincipalName,signInActivity,displayName" -Header $Header
$DevicesGuestUsers = $Result.value
while ($Result.'@odata.nextLink') {
    $Result = Invoke-RestMethod -Uri $Result.'@odata.nextLink' -Headers $Header
    $DevicesGuestUsers += $Result.value
}

$oldDevicesoldGuestUsers = @()
$failedDateDevicesfailedDateUsers = @()
foreach($inactiveDeviceGuestUser in $Devices)GuestUsers){
    try{
        $DateNonInteractiveDate = Get-Date($inactiveDevice.approximateLastSignInDateTime)GuestUser.signInActivity.lastNonInteractiveSignInDateTime)
        $InteractiveDate = Get-Date($GuestUser.signInActivity.lastSignInDateTime)
        if($DateNonInteractiveDate -lt ((Get-Date).AddMonths(-6)) -and ($InteractiveDate -lt ((Get-Date).AddMonths(-6)))){
            $oldDevicesoldGuestUsers += $inactiveDeviceGuestUser
        }
    } catch {
        $failedDateDevicesfailedDateUsers += $inactiveDeviceGuestUser
    }
}
Write-Warning "Date Errors: $($failedDateDevices.failedDateUsers.count)"

$failedDeleteDevicesfailedDeletedGuestUsers = @()
$deletedDevicesdeletedGuestUsers = @()
foreach($DeviceGuestUser in $oldDevices)oldGuestUsers){
    try{
        Remove-AzureADDevice$Result = Invoke-RestMethod -ObjectIdMethod $Device.idDELETE Write-Output-Uri "Deleted: https://graph.microsoft.com/v1.0/users/$($Device.displayName)GuestUser.id)" -Header $deletedDevicesHeader
        $deletedGuestUsers += $DeviceGuestUser
    }catch{
        $failedDeleteDevicesfailedDeletedGuestUsers += $DeviceGuestUser
    }
}
Write-Warning "Delete Errors: $($failedDeleteDevices.failedDeletedGuestUsers.count)"

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

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