PowerShell  automation cleanup

PowerShell automation cleanup


4 min read

Recently I had to do tedious work, i needed to cleanup dangling identities in my Azure subscriptions, but who would go one by one and remove them by hand... ๐Ÿ˜’

I decided that this issue could be great example where I could automate entire process, so when you work in enterprise environments, where everything is changing rapidly, you can have a ton of unmanaged identities which was removed at some point, however Azure UI, will still show them as present when you go to iam blade, whether that is subscription, resource group or resources itself, and it looks like this.

At first glance, you don't understand what is it, where it comes from, and why it's unknown, so this was confusing for me as well at first sight. After reading some documentation, I stumbled upon documentation page for greater context, so I highly recommend that you check this out.

So instead of checking every subscription, resource group, or resource manually, you can fire up the script, and script will behave in a manner where you will have to provide subscription ID, and your email account that corresponds to your Azure environment. Important thing to notice is that you would need to have Owner role or some custom elevated permission that will give you rights to manage RBAC, otherwise, this won't work.

Here you can find the script written in PowerShell core 7.4. Feel free to name your script as you wish, I will give it a name "cleanup.ps1"

# Enable debugging
# $DebugPreference = "Continue" 
$subscriptionId = Read-Host "Please enter subscription id"
$accountId = Read-Host "Please enter your email"
# Login to Azure
$context = Connect-AzAccount -AccountID $accountId -Subscription $subscriptionId

$resourcegroups = Get-AzResourceGroup -DefaultProfile $context

foreach ($group in $resourcegroups) {
    $groupName = $group.ResourceGroupName
    $scope = "/subscriptions/$subscriptionId/resourcegroups/$groupName"

    Write-Host "Listing all unknown identities within $groupName"
    $unknownIdentities = Get-AzRoleAssignment -DefaultProfile $context -Scope $scope | Where-Object { $_.ObjectType -eq "Unknown" }

    if ($unknownIdentities) {
        foreach ($identity in $unknownIdentities) {
            Write-Host "Scope: $($identity.Scope)" -ForegroundColor DarkRed -BackgroundColor Yellow -NoNewline
            Write-Host ", Role: $($identity.RoleDefinitionName)" -BackgroundColor Green -NoNewline
            Write-Host ", ObjectType: $($identity.ObjectType)" -BackgroundColor DarkMagneta -ForegroundColor White -NoNewline
            Write-Host ", ObjectId: $($identity.ObjectId)" -BackgroundColor Black -ForegroundColor White
    else {
        Write-Host "No unknown identities found in resource group $groupName"

    $confirmation = Read-Host -Prompt "Do you want to remove unknown identities from the $groupName ? (y/n)?"
    if ($confirmation -eq "y") {
        foreach ($identity in $unknownIdentities) {
            Get-AzRoleAssignment -Scope $scope | Where-Object { $_.ObjectType -eq "Unknown" } | Remove-AzRoleAssignment -Scope $scope
        Write-Host "All unknown identities removed from $groupName"
    else {
        Write-Host "Skipping $groupName, unknown identities still remain"

How to use the script?

run ./cleanup.ps1, script will ask you to provide your desired subscription id and your email. Providing your credentials on the Microsoft login page is necessary only when you first use your script as it will try to connect your AD account, after that everything should work in automated fashion.

Script will for_each resource groups within your subscription, so you will be prompted with results first, you will get colored output, so it can be easier for your eye, and you will be able to see the scope, role, Object type, and Object ID. Object type will always be "Unknown" so that is what we want to remove. If you are satisfied with results presented, you will be asked do you want to delete unknown identities from resource group <resource_group_name> (y/n)? If you are satisfied press y, and you will be shown confirmation with removed unknown identities, if you are not satisfied and you want to opt-out, press n, and script will reiterate to next resource group.

I just want to clarify one thing, you will be prompted with scope that is only scoped to subscription level or you will be prompted with scope that is set to resource directly? That is because of inheritance. So if you attach the role to the subscription level, it will be propagated to the resource group, or if you attach the role to the resource group it can be propagated to the resource itself. This can be confusing, but the script is designed to be smart enough to catch unknown identities and remove them from right scope so they don't leave dangling.

PS in case you need debug help, uncomment line 2 to have logs in the console ๐Ÿ‘Œ

Let me know what do you think about this, does it make your life easier and what can be done better?

Thank you for reading ๐Ÿ˜Š