Creating an Azure Function to Run PowerShell in the Cloud

Published:24 October 2024 - 5 min. read

Today’s sponsor is n8n, the AI-native workflow automation tool built for ITOps and DevSecOps. With 100+ templates to get you started quickly and a powerful visual editor, you can automate complex workflows without giving up control. Check it out here.

 

 

 

 

 

Azure Functions provides a powerful way to run code in the cloud without managing servers. Let’s create an Azure Function that runs PowerShell—perfect for automating tasks or building serverless applications.

Setting Up the Prerequisites

First, we need to set up some Azure resources. We’ll use PowerShell to create these:

1. Create a resource group:

New-AzResourceGroup -Name "PowerShellAzFunctionsDemo" -Location "East US"

2. Create a storage account (required for Azure Functions):

New-AzStorageAccount -ResourceGroupName "PowerShellAzFunctionsDemo" -Name "psazfunctionsdemostorage" -Location "East US" -SkuName "Standard_LRS"

Every Function App needs a storage account to store function code and other function-related content. We’re using Standard_LRS (Locally Redundant Storage) because it’s cost-effective for demo purposes. In a production environment, you might consider more redundant options.

3. Create the Function App:

New-AzFunctionApp -Name "PowerShellFunctionsDemo" -ResourceGroupName "PowerShellAzFunctionsDemo" -StorageAccountName "psazfunctionsdemostorage" -Runtime "PowerShell" -RuntimeVersion "7.2" -OSType "Linux" -Location "East US"

This is where the magic happens. We’re creating a Function App, which is the container that will host our functions. We’re specifying PowerShell 7.2 as our runtime to ensure our scripts run on PowerShell 7.2. We’re using Linux as our OS type because it’s generally more cost-effective and faster than Windows.

The function app contains all the functions you create.

Creating the Function Locally

Now that we have our Azure resources set up let’s create the function locally:

1. Install Azure Functions Core Tools (if not already installed).

2. Create a new function project:

func init PowerShellFunctionProject --powershell

3. Let’s see what it created.

gci C:\PowerShellFunctionProject
  • Host.json is a global configuration file that affects all functions within an app. It configures things like logging, function timeout, bindings, and triggers across functions.
  • The local.settings.json file is a configuration file for local development. You’ll typically work on your function code here locally and then publish it once complete.
  • profile.ps1 – This is a PowerShell script that runs when your function app starts or a new runspace is created. It’s used for global initialization tasks such as setting up authentication, importing modules, and defining variables available to all app functions.
  • requirements.psd1 – This PowerShell manifest file s lists PowerShell module dependencies for your function app. It specifies which modules and versions should be automatically installed, ensuring all necessary tools are available for your functions to run properly.

But no PowerShell scripts to invoke. That’s up to us to create.

4. Create a new function within the project. To create that, I need to create a subfolder inside of the project folder, which will be called the name of the function.

mkdir C:\PowerShellFunctionProject\PowerShellFunction

5. Create the run.ps1 script:

using namespace System.Net

param($Request, $TriggerMetadata)

$name = $Request.Query.Name

$body = "Hello, $name. This HTTP triggered function executed successfully."

Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = [HttpStatusCode]::OK
    Body = $body
})

This is the heart of our function – the actual PowerShell code that will run. This is a simple example that demonstrates input and output. When the function is triggered via HTTP (since we set that up in the function JSON file), the HTTP request will be represented via the Request parameter. The Request parameter will contain information like the HTTP verb used, such as GET or POST, the query, and the payload or body of the HTTP request for POST requests.

The script will read the Name HTTP query parameter, assign it the name variable, and then use it in a simple string.

Finally, the function will output information via HTTP, so we need to apply an output binding. The output binding will “bind” the PowerShell script output stream to the function to send back via an HTTP response. To convert the PowerShell output to an HTTP response that the function understands, we must create an HttpResponseContext object with two properties:

  1. StatusCode: Set to HTTP 200 OK, indicating a successful request.
  2. Body: The greeting message we constructed earlier.

This structure allows Azure Functions to properly format the HTTP response that will be returned to the client.

5. Now, let’s create the function.json file.

{
  "bindings": [
    {
      "name": "Request",
      "methods": [
        "get",
        "post"
      ],
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "type": "http",
      "name": "Response",
      "direction": "out"
    }
  ]
}

This function.json file is crucial. It defines the bindings for our function. Bindings specify how it’s triggered and how it outputs data.

  • We’re setting up an HTTP trigger that responds to GET and POST requests
  • This function will use anonymous authentication so we can query the URL it will be exposed to without providing any credentials.

The function will return an HTTP response and be called the $Response variable within the PowerShell script we’ll run.

This needs to be saved in the same folder as the run.ps1 script.

6. Now, navigate to the function folder first.

cd C:\PowerShellFunctionProject

7. And see if the function can start.

func start

When developing Azure Functions, you should first locally and then publish it when you’re finished. You can run a function locally by running func start.

Ah, another dependency. Don’t you love those? Let’s get the .NET Core SDK installed.

start https://www.microsoft.com/net/download

All done. Let’s give it another shot. But first, I need to restart my PowerShell console again and try again.

func start

…and we’re hitting another error. The red that sticks out to me is “failed to start a new language worker for runtime powershell”. A worker in Azure Functions speak is the host that executes PowerShell. Since we’re running locally, that’s my local machine. Let’s check the local settings JSON file.

code C:\PowerShellFunctionProject\local.settings.json

By default, it set the runtime version to 7.2. Let’s check the version installed on my machine.

$PSVersionTable

Ah, there’s the problem. I’m on 7.4. Let’s change that.

Now, let’s try that again.

func start

It’s much better. There’s no ugly red. Now, we have a URL we can trigger the script with. Let’s copy that URL and open another console.

Invoke-RestMethod -Uri http://localhost:7071/api/PowerShellFunction

I’ll use Invoke-RestMethod to query the local web server and see what happens.

Yay! The PowerShell text is shown. But wait, there’s nothing after ‘Hello’.

Invoke-RestMethod -Uri <http://localhost:7071/api/PowerShellFunction> -Body @{Name='Adam'}

Let’s pass the HTTP body with a key of Name that the script is looking at.

Success!

gc C:\PowerShellFunctionProject\PowerShellFunction\run.ps1

You can see where $Request.Query.Name is in the script here.

We now know that the function was triggered by the HTTP GET request we just executed to the local webserver. The function ran, executed the PowerShell script, and then sent the PowerShell script output back via an HTTP response.

Publishing the Azure Function

Our function is complete and tested locally; now let’s publish it to Azure and see if it works up there too. Let me first make sure I’m in my function project directory.

cd C:\PowerShellFunctionProject
Get-AzFunctionApp -Name "PowerShellFunctionsDemo" -ResourceGroupName "PowerShellAzFunctionsDemo"

Let me verify the name of the Function app I created earlier.

OK, good. It is PowerShellFunctionsDemo.

func azure functionapp publish PowerShellFunctionsDemo

Now I can use the func binary again and publish the function by specifying the name of the function app to push the local function to.

This process zips up all of the files in that function folder, uploads them to the storage account that’s hosting the function app and then deploys them to a little web app.

Looks good. Time for the final test!

Invoke-RestMethod -Uri 'https://powershellfunctionsdemo.azurewebsites.net/api/powershellfunction' -Body @{Name='Adam'}

Success! It’s now running precisely the same as it was on my local machine. Our Azure Function is deployed and available!

Conclusion

Congratulations! You’ve successfully created an Azure Function that runs PowerShell code in the cloud. This opens up a world of possibilities for serverless automation and scalable script execution.

Remember, this is just scratching the surface. Azure Functions can do much more, including handling different types of triggers (like timers or queue messages) and integrating with other Azure services. Happy scripting in the cloud!

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!