DEV Community

Cover image for Retrieving all tagged resources with PowerShell, Azure Functions and Managed Identity
Christos Matskas for The 425 Show

Posted on • Updated on

Retrieving all tagged resources with PowerShell, Azure Functions and Managed Identity

I'm always amazed how much power we have in our hands and how much we can achieve with only a few lines of code. During our regular stream yesterday on the #425Show we came across the following requirement: we needed to retrieve all resource groups in an Azure Subscription that were not tagged with a specific tag.

We can actually watch the recording here:

PowerShell to the rescue

I'm a big PowerShell fan, especially after Core as I can use it anywhere and everywhere. Even in Azure Functions! Therefore, my go-to solution to our problem was to write a PowerShell script that would run on an Azure Function. Unfortunately, we didn't manage to complete the task during the show so I took it upon me to finish up and write a nice blog post about it so you can use the same approach too.

Create the Azure Function with PowerShell

Firstly, make sure to update your Azure Function Core Tools. Get the instructions on how to install and run locally in this excellent doc

With the tools installed, we can go ahead and create our Azure Function. Open your favorite terminal and type:

func init
Make sure to select the right runtime - in this case PowerShell
Alt Text

The above command created the Function app, the shell of our Function. But it doesn't contain any code yet. For that, we need to create a function. In the terminal type:

func new

Alt Text

Implementing the logic in PowerShell

Let's open our Function in VS Code to do some editing. First, we want to define which dependencies we use. By default, PowerShell will pull the full Azure Powershell which is a lot. We only need a subset of these modules via a custom configuration in the requirements.psd1. Let's open that file and add the following dependencies:

    # For latest supported version, go to ''. 
    'Az.Accounts' = '1.*'
    'Az.Resources' = '2.*'
    'Az.ResourceGraph' = '0.7.7'

Let's close and save the file. Next and last step is to add the Function code to implement our logic

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

Connect-AzAccount -Identity

$resourceGroupsToTag = @()
$rgs = Get-AzResourceGroup

$expirestag = "expires"
foreach ($r in $rgs) {
    $tags = Get-AzTag -ResourceID $r.ResourceID
    try {
        if (-Not $tags.Properties.TagsProperty.ContainsKey($expirestag)) {
            $resourceGroupsToTag += $r.ResourceGroupName
    Catch {
        Write-Host "error " + $r.ResourceGroupName

$body = $resourceGroupsToTag

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = [HttpStatusCode]::OK
    Body = $body

If you want to know what each line does, make sure to use the VS Code Code Tour feature to explain how we implemented this line by line.

Alt Text

If you haven't used CodeTour before then you're missing out. It's an amazing Code extension that allows you to record and walkthrough with comments and then step through the tour. I love it, I believe it's transformational and I plan on using it more frequently going forward.

Install it today - link

Deploy the Azure Function using the VS Code extension, or whichever way you feel more comfortable (Azure DevOps or GitHub actions etc)

Configure the Managed Identity

The nice thing about our code is that we can authenticate and run the queries against our subscription without having to write any code, provide any accounts or credentials. To do this, we leverage the power of Azure Managed Identities

We can configure Managed Identities via PoSH, CLI or ARM etc. If you want to do it with PowerShell, the command is:

Update-AzFunctionApp -Name <YourFunctionAppName> -ResourceGroupName <YourResourceGroup> -IdentityType SystemAssigned
$managedIdentityId = (Get-AzureADServicePrincipal -SearchString '<yourFunctionAppName>').ObjectId
New-AzRoleAssignment -ObjectId $managedIdentityId -RoleDefinitionName "Contributor" -Scope "/subscriptions/<YourSubscriptionId"

If you prefer using the Azure portal to make it more visual, we have to go to the Azure Function App in the portal and click on the Identity tab. Ensure that we're in the System Assigned area (default).

Alt Text

We change the Status to On and hit Save. This will present us with the following message, which is what we want:

Alt Text

This takes a couple of seconds as it configure the App Registration for us in Azure Active Directory. If all worked well, we should see the following:

Alt Text

Now we have to configure the Role Assignments to allow our Managed Identity to pull the information we need in our PowerShell Function. Click on the Azure Role Assignments button and on the next page press the Add role assignment (Preview).

Alt Text

Make sure to select the right values

  • scope -> Subscription
  • subscription -> Needs to be the same as the one where our Managed Identity was created
  • role -> Reader should suffice as we're only querying our resources

Alt Text

Alt Text

And with these few steps, our Managed Identity is set up and ready to be put in good use :)

Putting it to the test

We are now ready to put our Azure Function to the test and retrieve some data. Navigate to your Function and open the Code + Test tab. Press the Run button and see the magic happen

Alt Text

Where is the code?

You can get the full code (including the Code Tour) on GitHub:


This was as fun little project that shows how you can use Managed Identity to securely run PowerShell code in Azure Functions. There are many cases where PowerShell is a lot more powerful and versatile in pulling Azure-related information so having a secure way to run it is invaluable.

We recently talked about this exact implementation on our Twitch stream. We also discussed alternative options using the Azure Resource Graph, which we plan to blog about here soon.

Or hit us up on Twitter: Christos John

Top comments (0)