Janne Mattila

From programmer to programmer -- Programming just for the fun of it

Automating maintenance tasks with Azure Functions and PowerShell - Part 2: Deployment

Posted on: November 6, 2023

In my previous blog post Automating maintenance tasks with Azure Functions and PowerShell - Part 1: Development I covered how you can create and develop these maintenance tasks in Azure Functions and PowerShell.

In this post I’ll cover how you can deploy these maintenance tasks to Azure.

Background

Before going to the actual deployment example, I recommend that you check out my previous posts on this topic to get some idea how I approach this topic:

Azure deployment entry point

To ARM, or not to ARM, that is the question

Surprisingly little has changed in this area since I wrote those posts.

Of course, Bicep has now taken the position of ARM templates.

So maybe it’s not a big surprise that in this post I’ll use Bicep to deploy our maintenance task infrastructure with the help of PowerShell.

Deployment

Let’s start by identifying our infrastructure needs for our maintenance task automation:

From the above list, majority of the resources are obvious and self-explanatory.

However, you might be wondering why we need two storage accounts. At the time of writing this post, Azure Functions does not yet support pulling deployment packages using managed identity. Therefore, I’ve split usage into two separate storage accounts. First storage account is used for the function app itself and it is protected by Entra ID. Function App connects to it using managed identity.

If I would like to connect to that storage account, then I would get this:

Above happens, because I don’t have required roles assigned to me in order to access that content.

If I would like to connect using access keys, then I would get this:

Above happens, because Shared Key authorization is disabled for that storage account.

Second storage account is used for the deployment resources and that is not protected by Entra ID:

Why would I prefer to use user-assigned managed identity instead of system-assigned managed identity?

I prefer to use user-assigned managed identities, because very often in large organizations you don’t have permissions to assign correct roles to the identities. Imagine requesting role assignments to management group level to modify resources. It might take time to get that approved. So, you easily end up waiting quite some time for someone to approve and assign that role to the identity.

Now if you suddenly need to delete your compute resources and create them again, you would have to ask that role assignment again if you use system-assigned managed identity. But since we use user-assigned managed identity, we can just use the existing identity with existing accesses without any problems. That applies even if we would have to change the architecture to use e.g., Azure Container Apps instead of Azure Functions.

Here are our resources to be deployed:

I have created Bicep files into deploy/MaintenanceTasks folder in our example repository:

And as I explained in the beginning of the post, the deployment is encapsulated behind deploy.ps1.

Before executing that script, you need to login to Azure and select the correct subscription:

Connect-AzAccount
Select-AzSubscription -SubscriptionName "Your subscription name"

Now you can deploy the resources by running below command (read instructions on how to create Incoming Webhooks to Teams to get the $webhookUrl):

$webhookUrl = "...your-incoming-webhook-url-to-teams.."
$result = .\deploy.ps1 -WebhookUrl $webhookUrl

$result contains the outputs from our Bicep deployment. You can study them by running the following commands:

$result.Outputs | Format-List
$result.Outputs["funcApp"].value
$result.Outputs["funcAppUri"].value

$funcApp = $result.Outputs["funcApp"].value
$funcAppUri = $result.Outputs["funcAppUri"].value

You can use Azure Functions Core Tools to publish the function app. I have here pushd and popd commands to change directory to the function app folder and back.

pushd ../../src/MaintenanceTasks/
func azure functionapp publish $funcApp
popd

But if you want to use the context from your Azure PowerShell session, then you can use this:

pushd ../../src/MaintenanceTasks/
$accessToken = (Get-AzAccessToken).Token
func azure functionapp publish $funcApp --access-token $accessToken
popd

Now you should see the function app in the portal:

Important: Now you should assign Virtual Machine Contributor (or similar role) which enables shutting down of VMs for our managed identity. Scope can be management group, subscription, or resource group.

Test

You can now invoke our deployed HTTP trigger using:

$subscriptionId = (Get-AzContext).Subscription.Id
$resourceGroupName = "rg-maintenance-tasks"
$functionName = "HttpScanVirtualMachines"

$functionKeys = Invoke-AzRestMethod -Path "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$funcApp/functions/$functionName/listkeys?api-version=2022-03-01" -Method POST

$code = ($functionKeys.content | ConvertFrom-Json).default

curl "https://$funcAppUri/api/ScanVirtualMachines?code=$code"

Here is an example output:

{
  "VirtualMachines": [
    {
      "Name": "vm-runbook-worker",
      "Subscription": "4....4",
      "ResourceGroup": "automation-account-rg",
      "Location": "westus3",
      "Schedule": "8-10"
    }
  ],
  "Continues": false
}

You can invoke the Timer trigger using:

$keys = Invoke-AzRestMethod -Path "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$funcApp/host/default/listkeys?api-version=2022-03-01" -Method POST
$masterKey = ($keys.content | ConvertFrom-Json).masterKey

curl --request POST -H "Content-Type: application/json" --data '{}' "https://$funcAppUri/admin/functions/TimerScanVirtualMachines?code=$masterKey"

Since we’re using application insights, then you should see the telemetry in the portal:

You can use them to create alerts if there are errors in the function app.

Deploying from pipelines and actions

GitHub has excellent Azure Functions Action marketplace page with links to e.g., Deploy PowerShell project to Azure Function App.

Azure DevOps has also Azure Functions task which is similarly documented here.

You can use one of the many documented ways to deploy your function app no matter which CI/CD tool you use.

Summary

Now you should have a good understanding of how you can deploy your maintenance tasks to Azure. I want to really repeat my previous statement that Azure Functions and PowerShell is a very powerful combo and if you’re not familiar with it then I recommend that you check it out.

I hope you find this useful!