- The Operation Validation Framework: Test your infrastructure using Pester - Mon, Jun 25 2018
- How to write an Azure Function in PowerShell - Tue, Jun 12 2018
- Process file paths from the pipeline in PowerShell functions - Mon, Jun 4 2018
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.
- Log in to Azure.
- Create a resource group.
- Create a storage account.
- Get the storage account connection string.
- Create a function app.
- Set the function app settings.
- Deploy the function.
- 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.
$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
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 $_ }
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 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
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"
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
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
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
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
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.
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?
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