Problem scenario
As a proof-of-concept you want to use an ARM template to create an Azure Function that uses Azure storage. You want the template to create the Azure storage account as it is a dependency of the Azure Serverless Function (also known as an App Service). How do you do this?
Overview
This will create an Azure Function also known as an App Service. The name of it will be entered interactively when the PowerShell script is run (when you are prompted for a "nameFromTemplate").
Prerequisites
1. If you are using Windows 7, you have installed Azure PowerShell; if you need assistance, see this posting. If you are using Windows 10, you have installed the modules for Azure and the storage resource; if you need assistance with this, see this posting.
2. You know your subscription ID. If you need assistance, see this posting.
Procedures
1. Create a template.json file with the following content (and remember its location for step #3):
{
"resources": [
{
"apiVersion": "2016-03-01",
"name": "[parameters('name')]",
"type": "Microsoft.Web/sites",
"properties": {
"name": "[parameters('name')]",
"siteConfig": {
"appSettings": [
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "node"
},
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '2015-05-01-preview').key1)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageName')), '2015-05-01-preview').key1)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[concat(toLower(parameters('name')), 'ba80')]"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "8.11.1"
}
]
},
"clientAffinityEnabled": false,
"reserved": false
},
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageName'))]"
],
"location": "North Central US",
"kind": "functionapp"
},
{
"apiVersion": "2015-05-01-preview",
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('storageName')]",
"location": "[parameters('location')]",
"properties": {
"accountType": "Standard_LRS"
}
}
],
"parameters": {
"name": {
"type": "string"
},
"storageName": {
"type": "string"
},
"location": {
"type": "string"
},
"subscriptionId": {
"type": "string"
}
},
"$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
"contentVersion": "1.0.0.0"
}
2. Create a parameters.json file, start with the "{" as the first line as a draft with the following content (and remember its location for step #3). Replace 1234-abcd-5678-efgh with your subscription ID, replace zzzxxxyyywww with what you want your storage account name to be:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"name": {
"value": "ne3c8dnmq40328"
},
"storageName": {
"value": "zzzxxxyyywww"
},
"location": {
"value": "North Central US"
},
"subscriptionId": {
"value": "1234-abcd-5678-efgh"
}
}
}
3. Use the following PowerShell script as a draft. Change the "path\to" on the lines that assign where the respective template.json and parameters.json files should be (so they match where you created them in the above two steps).
<#
.SYNOPSIS
Deploys a template to Azure
.DESCRIPTION
Deploys an Azure Resource Manager template
.PARAMETER subscriptionId
The subscription id where the template will be deployed.
.PARAMETER resourceGroupName
The resource group where the template will be deployed. Can be the name of an existing or a new resource group.
.PARAMETER resourceGroupLocation
Optional, a resource group location. If specified, will try to create a new resource group in this location. If not specified, assumes resource group is existing.
.PARAMETER deploymentName
The deployment name.
.PARAMETER templateFilePath
Optional, path to the template file. Defaults to template.json.
.PARAMETER parametersFilePath
Optional, path to the parameters file. Defaults to parameters.json. If file is not found, will prompt for parameter values based on template.
#>
param(
[Parameter(Mandatory=$True)]
[string]
$subscriptionId,
[Parameter(Mandatory=$True)]
[string]
$resourceGroupName,
[string]
$resourceGroupLocation,
[Parameter(Mandatory=$True)]
[string]
$deploymentName,
[string]
$templateFilePath = "C:\Users\jdoe\Documents\WindowsPowerShell\c\template.json",
[string]
$parametersFilePath = "C:\Users\jdoe\Documents\WindowsPowerShell\c\parameters.json"
)
<#
.SYNOPSIS
Registers RPs
#>
Function RegisterRP {
Param(
[string]$ResourceProviderNamespace
)
Write-Host "Registering resource provider '$ResourceProviderNamespace'";
Register-AzureRmResourceProvider -ProviderNamespace $ResourceProviderNamespace;
}
#******************************************************************************
# Script body
# Execution begins here
#******************************************************************************
$ErrorActionPreference = "Stop"
# sign in
Write-Host "Logging in...";
Login-AzureRmAccount;
# select subscription
Write-Host "Selecting subscription '$subscriptionId'";
Select-AzureRmSubscription -SubscriptionID $subscriptionId;
# Register RPs
$resourceProviders = @("microsoft.web","microsoft.storage");
if($resourceProviders.length) {
Write-Host "Registering resource providers"
foreach($resourceProvider in $resourceProviders) {
RegisterRP($resourceProvider);
}
}
#Create or check for existing resource group
$resourceGroup = Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
if(!$resourceGroup)
{
Write-Host "Resource group '$resourceGroupName' does not exist. To create a new resource group, please enter a location.";
if(!$resourceGroupLocation) {
$resourceGroupLocation = Read-Host "resourceGroupLocation";
}
Write-Host "Creating resource group '$resourceGroupName' in location '$resourceGroupLocation'";
New-AzureRmResourceGroup -Name $resourceGroupName -Location $resourceGroupLocation
}
else{
Write-Host "Using existing resource group '$resourceGroupName'";
}
# Start the deployment
Write-Host "Starting deployment...";
if(Test-Path $parametersFilePath) {
New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $templateFilePath -TemplateParameterFile $parametersFilePath;
} else {
New-AzureRmResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile $templateFilePath;
}
echo "If you are prompted for a nameFromTemplate (regarding a new-azurermresourcegroupdeployment), enter any arbitrary string.*"
echo "* The string must not already exist in the Microsoft Azure websites; so it must be unique. It will be the name of your App Service"
echo "The above two-line FYI is for when you first run this script. When the script is otherwise complete, ignore it."
In the future we want to re-write these directions to use write-verbose instead of write-host. We want to warn people that write-verbose is recommended instead of write-host (according to this posting).