Janne Mattila

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

Automating maintenance tasks with Azure Functions and PowerShell - Part 1: Development

Posted on: October 30, 2023

PowerShell has become a very popular way of automating different installations or maintenance tasks. Read my previous blog post about Onboarding multiple Arc-enabled servers with the help of map file. In that post I used PowerShell to extend the installation functionality.

PowerShell is also frequently used in various automation and maintenance tasks in Azure development. Scripting can be used for scanning resources, stopping virtual machines, scaling automation or clean up tasks.

Azure Automation Account has been traditionally used for hosting these automations, but Azure Functions has taken that position many times in the last years. I think Azure Functions with PowerShell and managed identities is an excellent combination for running these various automation tasks.

But before rushing to the example in this post, please check the official documentation for more information if you’re not familiar with Azure Functions and PowerShell:

Azure Functions Overview

PowerShell developer reference for Azure Functions

Create a PowerShell function using Visual Studio Code

Azure Functions HTTP triggers and bindings

Timer trigger for Azure Functions

Maintenance tasks example

In this post I’ll cover one example end-to-end so that you get good idea how you can use it to run your own automations.

The scenario for our automation is the following:

A couple of important things I want to highlight in these kinds of automations:

Please split the functionality to Azure Functions aware code and plain vanilla Azure PowerShell code. Azure Functions aware code manages the triggers (e.g., Timer, HTTP), output bindings and fetches the required parameters from environment variables or any other place so that it can then pass them on to the plain vanilla Azure PowerShell script as PowerShell parameters.

Above is important because it enables you to do much faster local development when you do that plain vanilla Azure PowerShell automation directly without any dependency to the Azure Functions Runtime. To improve your scripting, I recommend reading my VS Code and faster script development blog post.

Plain vanilla Azure PowerShell can be easily tested using pester test framework. Similarly, you can use any other scripting tools to test and validate your code. Additionally, now you have portability of that code. It can be executed manually on your local machine or in any other place and not just in Azure Functions.


To get quickly started, here are commands for generating the basic project structure:

# Initialize Azure Functions project
func init MaintenanceTasks --worker-runtime powershell
cd MaintenanceTasks

# Create HTTP Trigger (for ad-hoc use cases)
func new --name HttpScanVirtualMachines --template "HTTP trigger" --authlevel "function"

# Create Timer Trigger (for scheduled usage)
func new --name TimerScanVirtualMachines --template "Timer trigger"

# Create folder for our business logic (shared between triggers)
mkdir Scripts
echo "# Code here" > Scripts/ScanVirtualMachines.ps1

# Create folder for our pester tests
mkdir Tests
echo "# Tests here" > Tests/ScanVirtualMachines.Tests.ps1

# Open in VS Code
code .

Project structure follows naming convention Trigger type + Function name.

To run this locally, you need to start Azure Storage account emulator Azurite:

azurite --location $env:TEMP\azurite

Then you’re ready to start the Azure Functions:

func start

Now you’re ready to start developing your automation scripts to the Scripts folder and in this example case it will be ScanVirtualMachines.ps1.

Here is the logic for scanning virtual machines:

Now you can call this logic from both triggers. Here is the code from HttpScanVirtualMachines:

$response = . $env:FUNCTIONS_APPLICATION_DIRECTORY/Scripts/ScanVirtualMachines.ps1 -Count $count

Similarly, here is the code from TimerScanVirtualMachines:

$response = . $env:FUNCTIONS_APPLICATION_DIRECTORY/Scripts/ScanVirtualMachines.ps1 -Count 1000 -ForceShutdown

Notice that we’re passing different parameters to the script based on the trigger type.

HTTP trigger returns the $response as JSON:

Timer trigger will force shutdown virtual machines that are running out of allowed schedule by using -ForceShutdown parameter. It also processes $response and creates markdown output and sends message to Teams channel about the virtual machines that have been shut down. For that Incoming Webhooks is used. You can find more information about that from the documentation.

$env:FUNCTIONS_APPLICATION_DIRECTORY is environment variable which provides easy way to have full path to the script and you don’t have to rely on the current working directory.

Here is an example of the message that is sent to Teams channel:

You can invoke these triggers from command-line using:

# HTTP trigger
curl http://localhost:7071/api/ScanVirtualMachines
curl http://localhost:7071/api/ScanVirtualMachines?count=1000

# Timer trigger
curl --request POST -H "Content-Type: application/json" --data '{}' http://localhost:7071/admin/functions/TimerScanVirtualMachines

Here is our solution in VS Code:

Source for this demo can be found here:

Summary

This post demonstrated one example of how you can use Azure Functions and PowerShell for managing your maintenance tasks. You can use this same model for many other use cases as well.

Next step is to deploy this solution to Azure. Read about it here:

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

I hope you find this useful!