Serverless computing—or the ability to execute code without having to manage the underlying resources—is all the rage these days. Can PowerShell join in on the fun? Yes, it can! This article will show you how to use PowerShell to create an Azure Functions app and deploy a PowerShell-based function.
Avatar

What are Azure Functions

Azure Functions is a computing model in Microsoft Azure that allows you to execute small pieces of code or functions in response to events. There is no server infrastructure for you to manage, hence the term "serverless."

At the end of the day, a server somewhere runs your code, but you needn't worry about it. The benefit of Azure Functions is that you just need to worry about the problem at hand, not the underlying infrastructure. This frees you from wasting extra cycles on needless maintenance tasks like OS upgrades and patching.

Just write the code to do your thing and move on. The consumption-based billing plan only bills you for per-second resource consumption and the number of executions. With the generous free grant of 1 million executions and 400,000 GB seconds a month, there is also a good chance your function will be free or nearly free.

The deployment process

To deploy Azure Functions, we need to provision a few resources in Azure. Functions are a subcomponent of Azure App Services, so the same resources required for App Services are also necessary for functions. The rest of the article will describe our example function and the eight steps below to deploy the function app and test it.

  1. Log in to Azure.
  2. Create a resource group.
  3. Create a storage account.
  4. Get the storage account connection string.
  5. Create a function app.
  6. Set the function app settings.
  7. Deploy the function.
  8. Test the function.

The Azure function

Let's look at the PowerShell function we'll deploy. The HelloWorld folder has two files we'll deploy to the function app. The file run.ps1 is the actual function we'll execute. All of your function logic will be in this file. The function.json file contains binding settings that control invoking the function. The Deploy.ps1 file is the script that contains all of our steps to deploy and test the function app.

Folder layout

Folder layout

$requestBody = Get-Content $req -Raw | ConvertFrom-Json

# Get request body or query string parameter
if ($req_query_name) {
    $name = $req_query_name
} else {
    $name = $requestBody.name
}

$response = @{
    time = [System.DateTime]::UtcNow.ToString('u')
    Message = "Hello $name"
} | ConvertTo-Json

Out-File -InputObject $response -FilePath $res -Encoding Ascii

In an HTTP-triggered function, the function app will insert the incoming HTTP request object as a JSON string called $req. Notice we didn't define this variable first. Azure Functions makes this variable available to us automatically. We'll convert this to a PowerShell object using ConvertFrom-Json so we can work the data easier.

When triggering this function via HTTP, the query string passed by the user will be available to us. For example, if our function URI was https://4sysops-func.azurewebsites.net/api/HelloWorld?name=Brandon, the query string section would be name=Brandon. The function app will dynamically create variables called $req_query_<parameter-name> for each query string parameter provided.

When triggering the function from an HTTP POST, we can retrieve any properties passed as part of the HTTP message body as well. These are usually form data sent as key/value pairs, a JSON string, or an XML message. Any properties passed will be part of our $req variable Azure dynamically provides us.

Since we can pass these properties c to the function in several ways, we need to handle each case and set the value in our $name variable.

We'll then create a JSON response object. You can put any value you like in here. In this example, we're returning the current time in UTC and a message that includes the name parameter from the request. Next, we write this JSON response to another dynamically created variable the function app creates called $res using Out-File. The function app will send the contents of this file back in the HTTP response.

{
    "bindings": [
        {
            "name": "req",
            "type": "httpTrigger",
            "direction": "in",
            "authLevel": "function"
        },
        {
            "name": "res",
            "type": "http",
            "direction": "out"
        }
    ],
    "disabled": false
}

In function.json we define how to trigger the function. In this example, we're setting the function to trigger via HTTP and use function-level authentication. This means triggering the function requires a token provided as a query parameter to the function URI. You'll see how this works later after deploying the function. Microsoft has detailed documentation on how to configure triggers and bindings if you would like to customize triggering the function.

Create a function app

Now let's go on to creating the function app and deploying our function. We're going to do this entirely in PowerShell.

Log in to Azure

First, we need to log into Azure and set our action subscription. Be sure to specify your own subscription ID here.

# Login to Azure and set subscription
$subscriptionId = '<YOUR-SUBSCRIPTION-ID>'
Login-AzureRmAccount > $null
Select-AzureRmSubscription -SubscriptionID $subscriptionId
Log in to Azure

Log in to Azure

Register resource providers

Next, we'll verify registration of the required Azure resource providers. Azure Functions require the Microsoft.Web and Microsoft.Storage providers. Most likely, the providers are already registered, but this will make sure of that.

# Register resource providers
@('Microsoft.Web', 'Microsoft.Storage') | ForEach-Object {
    Register-AzureRmResourceProvider -ProviderNamespace $_
}
Register resource providers

Register resource providers

Create a resource group

We can then create a new resource group in the WestUS region. We'll create the required resources for the function app in this resource group. You can specify a different region if you like.

# Create resource group
$resourceGroupName = '4sysops-func-demo'
$location = 'westus'
$resourceGroup = Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
if (-not $resourceGroup) {
    $resourceGroup = New-AzureRmResourceGroup -Name $resourceGroupName -Location $location
}
$resourceGroup
Create a resource group

Create a resource group

Create a storage account

Azure Functions need a storage account for the deployed code to live in. Storage accounts also need a globally unique name, so we'll take the first section of a GUID and append it to the storage account name. That should be suitable to make it globally unique.

# Create storage account
$rnd = (New-Guid).ToString().Split('-')[0]
$storageAccountName = "4sysops$rnd"
$storageSku = 'Standard_LRS'
$newStorageParams = @{
    ResourceGroupName = $resourceGroupName
    AccountName       = $storageAccountName
    Location          = $location
    SkuName           = $storageSku
}
$storageAccount = New-AzureRmStorageAccount @newStorageParams
$storageAccount
Create a storage account

Create a storage account

Get storage account connection string

Since the function app requires a storage account, we need to retrieve the connection string. The function app will use this value to authenticate to the storage account and store the function code there.

# Get storage account key and create connection string
$accountKey = Get-AzureRmStorageAccountKey -ResourceGroupName $resourceGroupName -AccountName $storageAccountName |
    Where-Object {$_.KeyName -eq 'Key1'} | Select-Object -ExpandProperty Value
$storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=$storageAccountName;AccountKey=$accountKey"
Get storage account connection string

Get storage account connection string

Create function app

We can then create a new function app in the resource group. We'll use the command New-AzureRMResource to create an Azure resource of type Microsoft.Web/Sites and specify the kind as functionapp.

# Create the Function App
$functionAppName = '4sysops-func'
$newFunctionAppParams = @{
    ResourceType      = 'Microsoft.Web/Sites'
    ResourceName      = $functionAppName
    Kind              = 'functionapp'
    Location          = $location
    ResourceGroupName = $resourceGroupName
    Properties        = @{}
    Force             = $true
}
$functionApp = New-AzureRmResource @newFunctionAppParams
$functionApp
Create function app

Create function app

Set function app settings

After deployment of the function app, we need to provide connection details to our newly created storage account and use Set-AzureRmWebApp to apply the settings to the function app.

# Set Function app settings
$functionAppSettings = @{
    AzureWebJobDashboard                     = $storageConnectionString
    AzureWebJobsStorage                      = $storageConnectionString
    FUNCTIONS_EXTENSION_VERSION              = '~1'
    WEBSITE_CONTENTAZUREFILECONNECTIONSTRING = $storageConnectionString
    WEBSITE_CONTENTSHARE                     = $storageAccountName
}
$setWebAppParams = @{
    Name = $functionAppName
    ResourceGroupName = $resourceGroupName
    AppSettings = $functionAppSettings
}
$webApp = Set-AzureRmWebApp @setWebAppParams
$webApp
Set function app settings

Set function app settings

Deploy the function

To deploy our PowerShell-based function, we need to give it a name. In this example, we'll use HelloWorld. We also need to read the contents of the run.ps1 script and our function binding settings. We can then construct a new Azure resource ID using our function app's resource ID as a base. We then create a new hashtable containing our binding settings and the string content of the function code itself. Then we can execute New-AzureRmResource to create the function.

# Test function
$getSecretsParams = @{
    ResourceId = $function.ResourceId
    Action     = 'listsecrets'
    ApiVersion = '2015-08-01'
    Force      = $true
}
$functionSecrets = Invoke-AzureRmResourceAction @getSecretsParams

# GET
Invoke-RestMethod -Uri "$($functionSecrets.trigger_url)&name=Brandon"

# POST
$body = @{
    name = 'Brandon'
} | ConvertTo-Json
Invoke-RestMethod -Uri $functionSecrets.trigger_url -Body $body -Method Post
Deploy the Azure function

Deploy the Azure function

Test the function

After deployment, we can test whether the function works. First, we need to retrieve the function's URI and token. We're using function-level authentication, so each function you deploy has a specific token just for it. We need to retrieve that value along with the URI to trigger it. To do this, we can run the Invoke-AzureRmResourceAction command, passing in the function's resource ID and specifying the action as listsecrets.

Once we have the function's URI and token, we can use Invoke-RestMethod to verify the function operates correctly. You can see that in a GET request, we're using a query string parameter for name, and when using a POST, we're sending the name parameter in the request body.

In both cases, our function is working.

# Test function
$getSecretsParams = @{
    ResourceId = $function.ResourceId
    Action     = 'listsecrets'
    ApiVersion = '2015-08-01'
    Force      = $true
}
$functionSecrets = Invoke-AzureRmResourceAction @getSecretsParams

# GET
Invoke-RestMethod -Uri "$($functionSecrets.trigger_url)&name=Brandon"

# POST
$body = @{
    name = 'Brandon'
} | ConvertTo-Json
Invoke-RestMethod -Uri $functionSecrets.trigger_url -Body $body -Method Post
Test the Azure function

Test the Azure function

Conclusion

Azure Functions are great tools to have in your toolbox. The ability to trigger a PowerShell script from an HTTP request allows for some interesting use cases. And since this is all serverless, we needn't patch, back up, or maintain any infrastructure ourselves. We can just focus on the code. Since the billing is consumption-based, you only pay for what you use as well. Ultimately, this allows us to spend more time on what we should be doing—delivering value to the business.

2 Comments
  1. Avatar
    dunc 5 years ago

    This is an incredible guide, thank you.

    Under the “Deploy your function” heading however you’ve accidentally copied in the “Test” segment of code. The screenshot shows the correct deployment code.

    Could you update this for easier C&P?

  2. Avatar
    vinay 5 years ago

    Hi Brendan,

    Thanks for this example and its as you have described.

    I am just adding the deploy.ps1 code as per print screen so other can just copy and paste.

    #Deploy the code
    $functionName = ‘HelloWorld’
    $functionContent = Get-Content ./HelloWorld/run.ps1 -raw
    $functionSettings = Get-Content ./helloWorld/function.json |ConvertFrom-Json
    $functionResourceId = ‘{0}/functions/{1}’ -f $functionapp.resourceid, $functionName
    $functionProperties =@{
    config = @{bindings = $functionSettings.bindings
    }
    files = @{
    ‘run.ps1’ = “$functionContent”
    }
    }

    $newFunctionParams = @{
    ResourceID = $functionResourceId
    Properties = $functionProperties
    Apiversion = ‘2015-08-01’
    Force = $true
    }

    $function = New-AzureRmResource @NewfunctionParams
    $function

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account