Is it possible to read JSON from a file in ARM templates? - azure

Lets say I want to deploy a Logic App using an ARM template. Here is a part of my azuredeploy.json:
...
parameters: {
"logic-app-definition": {
"type": "string",
"metadata": {
"description": "The JSON definition of the logic app."
}
}
},
resources: [
{
"apiVersion": "2016-06-01",
"name": "lapp-my-sample",
"type": "Microsoft.Logic/workflows",
"location": "[resourceGroup().location]",
"properties": {
"definition": "[json(parameters('logic-app-definition'))]",
"state": "Enabled"
}
]
As you can see, the actual JSON definition of the Logic App will be taken from a string-paraemter. This is pretty uncomfortable because the template-JSON is mostly a one-line-JSON-mess mostly.
I wonder if there is a function to read the string-value from a file instead.

There's no way to readFromUri() or anything that direct but you don't need to keep your source in a string or single-line JSON file. Depends a bit on your orchestration (e.g. how you deploy) you can do some manipulation there and pass in a parameter or pull from a "config store" (appConfigStore, keyVault).
Happy to explore some options if you want to...

After #bmoore-msft's answer I decided to share my imprefect solution with the community.
First a simplified version of my deploy.ps1 for brevity:
# the initial stuff like parameters and connectivity etc.
# read file content and perform regex
$ParameterFileContent = Get-Content $TemplateParametersFile
$fileName = [regex]::Matches($ParameterFileContent, "getFileJson\('(.*?)'").captures.groups[1].value
$jsonContent = (Get-Content $fileName -Raw).Replace("`n","").Replace(" ", "")
$jsonContent = [regex]::Matches($jsonContent, '{"definition":(.*)}{1}$').captures.groups[1].value
$jsonContent = $jsonContent.Replace('"', '\"')
$result = [regex]::Replace($ParameterFileContent, "[\[]getFileJson\('(.*?)'\)[\]]", $jsonContent)
Set-Content $TemplateParametersFile -Value $result
# perform deployment
New-AzResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
-ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
-Force -Verbose `
# reset the template file to the original version
Set-Content $TemplateParametersFile -Value $ParameterFileContent
here then is my template parameter file:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
...
"logic-app-definition": {
"value": "[getFileJson('logicApp.json')]"
}
}
}
And finally here is my json template file for the logic app (logicapp.json):
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {
"Condition": {
"actions": {},
"expression": {
"and": [
{ "equals": ["#outputs('HTTP')['statusCode']", 200] }
]
},
"runAfter": { "HTTP": ["Succeeded"] },
"type": "If"
},
"HTTP": {
"inputs": {
"headers": { "Accept": "application/json" },
"method": "POST",
"uri": "https://myuri/"
},
"runAfter": {},
"type": "Http"
}
},
"contentVersion": "1.0.0.0",
"outputs": {},
"parameters": {},
"triggers": {
"Recurrence": {
"recurrence": { "frequency": "Hour", "interval": 6 },
"type": "Recurrence"
}
}
},
"parameters": {}
}
So I basically create a new magic "function" getFileJson and then I use RegEx before I deploy this thing.

It sounds like what you are trying to do can be done using linked templates:
https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#linked-template
This way you can keep the Logic App template in a separate file. You can still use parameters to deploy.

Related

ARM, how to make parameter file dependent on other parameter

I'm trying to make my parameter file a bit smarter but for the life of me can't figure out how to do so. I have a parameters.json file with 2 params: env & commonTags. env takes a string from my DevOps pipeline, and I need this parameter to fill a value in the commonTags-object parameter. See code snippet below:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"env": {
},
"location": {
"value": "westeurope"
},
"commonTags": {
"value": {
"contact":"dr#balloon.com",
"costcenter": "99999",
"env": "[parameters('env')]",
"criticality": "[parameters('env') == 'prd' ? 'high', 'low']"
}
}
}
}
The only other option I see is to set the env-specific parameters in the template file. Either by merging with the existing parameters or by setting the value of commonTags in the template file entirely. But I'd rather keep my template files free of parameter values and have these all located in a central parameter file.
Can anybody point me in the right direction? I can't seem to find anything online.
Many thanks!
You cannot nest parameters as you are trying to do here.
Instead, you can make use of conditional directly in your main arm template coupled with inline parameters. Let's take a simple Storage Account as an example.
azuredeploy.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string"
},
"env": {
"type": "string"
},
"costcenter": {
"type": "string"
},
"contact": {
"type": "string"
},
"storageAccountType": {
"type": "string",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS",
"Premium_LRS"
],
"metadata": {
"description": "Storage Account type"
}
}
},
"variables": {
"storageAccountName": "[concat('store', uniquestring(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageAccountType')]"
},
"tags": {
"contact": "[parameters('contact')]",
"costcenter": "[parameters('costcenter')]",
"env": "[parameters('env')]",
"criticality": "[if(equals(parameters('env'),'prd'),'high','low')]"
},
"kind": "StorageV2"
}
]
}
azuredeploy.parameters.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountType": {
"value": "Standard_LRS"
},
"costcenter": {
"value": "99999"
},
"contact": {
"value": "dr#balloon.com"
},
"location": {
"value": "westeurope"
}
}
}
Note that the tag names remain static - there are ways to pass them inline instead/as well - see this answer.
From these, you can then use Az CLI or Powershell to deploy your template, the parameters kept in a single place and the dynamically provide the remaining one like env.
Example using Powershell :
New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName $rgName -TemplateFile .\azuredeploy.json -TemplateParameterFile .\azuredeploy.parameters.json -env $env

Unable to connect the API connection to the logic App via ARM template in terraform

In my terraform I have created a logic app and its workflow with the help of a ARM Template. The 2 connections used in the logic app is also created via ARM template. But somehow even though the resources get created in AZURE. But when I got to the logic app, I always have to manually update the connection in the workflow. How can we make it automatic.
//First connection
resource "azurerm_template_deployment" "exampleeventhub" {
name = "acctesttemplate-44"
resource_group_name = Resourcegrpname
template_body = <<DEPLOY
{
"$schema": https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#,
"contentVersion": "1.0.0.0",
"parameters": {
"connections_eventhubs_name": {
"defaultValue": "eventhubs",
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Web/connections",
"apiVersion": "2016-06-01",
"name": "[parameters('connections_eventhubs_name')]",
"location": "qwerty",
"kind": "V1",
"properties": {
"displayName": "eventhubconnection",
"statuses": [
{
"status": "Connected"
}
],
"customParameterValues": {},
"nonSecretParameterValues": {},
"createdTime": "aaaaa",
"changedTime": "bbbb",
"api": {
"name": "[parameters('connections_eventhubs_name')]",
"displayName": "Event Hubs",
"description": "Connect to Azure Event Hubs to send and receive events.",
"iconUri": "[concat('https://connectoricons-prod.azureedge.net/releases/v1.0.1480/1.0.1480.2454/', parameters('connections_eventhubs_name'), '/icon.png')]",
"brandColor": "#c4d5ff",
"id": "[concat('/subscriptions/1111/providers/Microsoft.Web/locations/qwerty/managedApis/', parameters('connections_eventhubs_name'))]",
"type": "Microsoft.Web/locations/managedApis"
},
"testLinks": []
}
}
]
}
DEPLOY
deployment_mode = "Incremental"
}
//Second connection
resource "azurerm_template_deployment" "exampledatacollector" {
name = "acctesttemplate-45"
resource_group_name = Resourcegrpname
template_body = <<DEPLOY
{
"$schema": https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#,
"contentVersion": "1.0.0.0",
"parameters": {
"connections_thengadatacollector_name": {
"defaultValue": "thengadatacollector",
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Web/connections",
"apiVersion": "2016-06-01",
"name": "[parameters('connections_thengadatacollector_name')]",
"location": "qwerty",
"kind": "V1",
"properties": {
"displayName": "azuredatacollector",
"statuses": [
{
"status": "Connected"
}
],
"customParameterValues": {},
"nonSecretParameterValues": {
"username": "764a2b1e-431d-4e90-87b1-ea6a34dac48f"
},
"createdTime": "aaaa",
"changedTime": "bbbb",
"api": {
"name": "[parameters('connections_thengadatacollector_name')]",
"displayName": "Azure Log Analytics Data Collector",
"description": "Azure Log Analytics Data Collector will send data to any Azure Log Analytics workspace.",
"iconUri": "[concat('https://connectoricons-prod.azureedge.net/releases/v1.0.1480/1.0.1480.2454/', parameters('connections_thengadatacollector_name'), '/icon.png')]",
"brandColor": "#0072C6",
"id": "[concat('/subscriptions/1111/providers/Microsoft.Web/locations/qwerty/managedApis/', parameters('connections_thengadatacollector_name'))]",
"type": "Microsoft.Web/locations/managedApis"
},
"testLinks": []
}
}
]
}
DEPLOY
deployment_mode = "Incremental"
}
//Logic App
resource "azurerm_template_deployment" "example" {
name = "acctesttemplate-46"
resource_group_name = Resourcegrpname
template_body = <<DEPLOY
{
"$schema": https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#,
"contentVersion": "1.0.0.0",
"parameters": {
"workflows_logicapp_name": {
"defaultValue": "logicapp",
"type": "String"
},
"connections_thengadatacollector_externalid": {
"defaultValue": "/subscriptions/1111/resourceGroups/Resourcegrpname/providers/Microsoft.Web/connections/azureloganalyticsdatacollector",
"type": "String"
},
"connections_eventhubs_externalid": {
"defaultValue": "/subscriptions/1111/resourceGroups/Resourcegrpname/providers/Microsoft.Web/connections/eventhubs",
"type": "String"
}
},
"variables": {},
"resources": [
{
"type": "Microsoft.Logic/workflows",
"apiVersion": "2017-07-01",
"name": "[parameters('workflows_logicapp_name')]",
"location": "qwerty",
"properties": {
"state": "Enabled",
"definition": {
"$schema": https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#,
"contentVersion": "1.0.0.0",
"parameters": {
"$connections": {
"defaultValue": {},
"type": "Object"
}
},
"triggers": {
"When_events_are_available_in_Event_Hub": {
"recurrence": {
"frequency": "Minute",
"interval": 3
},
"splitOn": "#triggerBody()",
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "#parameters('$connections')['eventhubs']['connectionId']"
}
},
"method": "get",
"path": "/#{encodeURIComponent('thengaeventhub')}/events/batch/head",
"queries": {
"contentType": "application/octet-stream",
"maximumEventsCount": 50
}
}
}
},
"actions": {
"Send_Data_2": {
"runAfter": {},
"type": "ApiConnection",
"inputs": {
"body": "#base64ToString(triggerBody()?['ContentData'])",
"headers": {
"Log-Type": "testcustimlog"
},
"host": {
"connection": {
"name": "#parameters('$connections')['thengadatacollector_1']['connectionId']"
}
},
"method": "post",
"path": "/api/logs"
}
}
}
},
"parameters": {
"$connections": {
"value": {
"thengadatacollector_1": {
"connectionId": "[parameters('connections_thengadatacollector_externalid')]",
"connectionName": "thengadatacollector",
"id": "/subscriptions/1111/providers/Microsoft.Web/locations/qwerty/managedApis/thengadatacollector"
},
"eventhubs": {
"connectionId": "[parameters('connections_eventhubs_externalid')]",
"connectionName": "eventhubs",
"id": "/subscriptions/1111/providers/Microsoft.Web/locations/qwerty/managedApis/eventhubs"
}
}
}
}
}
}
]
}
DEPLOY
deployment_mode = "Incremental"
}
It is an expected behaviour , if you deploy the ARM Template, your both API Connections will have been created but inside logic apps you will have to update manually the connection by entering your credentials for the service. This is because for finalizing the API connection you need to give the consent but which is not possible in ARM template.
But if you need to finalize the API Connection creation without opening every Logic Apps then you can use PowerShell script .This script will retrieve a consent link for a connection for an OAuth Logic Apps connector. It will then open the consent link and complete authorization to enable a connection.
Param(
[string] $ResourceGroupName = 'YourRG',
[string] $ResourceLocation = 'eastus | westus | etc.',
[string] $api = 'office365 | dropbox | dynamicscrmonline | etc.',
[string] $ConnectionName = 'YourConnectionName',
[string] $subscriptionId = '80d4fe69-xxxx-xxxx-a938-9250f1c8ab03',
[bool] $createConnection = $true
)
#region mini window, made by Scripting Guy Blog
Function Show-OAuthWindow {
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object -TypeName System.Windows.Forms.Form -Property #{Width=600;Height=800}
$web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property #{Width=580;Height=780;Url=($url -f ($Scope -join "%20")) }
$DocComp = {
$Global:uri = $web.Url.AbsoluteUri
if ($Global:Uri -match "error=[^&]*|code=[^&]*") {$form.Close() }
}
$web.ScriptErrorsSuppressed = $true
$web.Add_DocumentCompleted($DocComp)
$form.Controls.Add($web)
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() | Out-Null
}
#endregion
#login to get an access code
Login-AzureRmAccount
#select the subscription
$subscription = Select-AzureRmSubscription -SubscriptionId $subscriptionId
#if the connection wasn't alrady created via a deployment
if($createConnection)
{
$connection = New-AzureRmResource -Properties #{"api" = #{"id" = "subscriptions/" + $subscriptionId + "/providers/Microsoft.Web/locations/" + $ResourceLocation + "/managedApis/" + $api}; "displayName" = $ConnectionName; } -ResourceName $ConnectionName -ResourceType "Microsoft.Web/connections" -ResourceGroupName $ResourceGroupName -Location $ResourceLocation -Force
}
#else (meaning the conneciton was created via a deployment) - get the connection
else{
$connection = Get-AzureRmResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $ResourceGroupName -ResourceName $ConnectionName
}
Write-Host "connection status: " $connection.Properties.Statuses[0]
$parameters = #{
"parameters" = ,#{
"parameterName"= "token";
"redirectUrl"= "https://ema1.exp.azure.com/ema/default/authredirect"
}
}
#get the links needed for consent
$consentResponse = Invoke-AzureRmResourceAction -Action "listConsentLinks" -ResourceId $connection.ResourceId -Parameters $parameters -Force
$url = $consentResponse.Value.Link
#prompt user to login and grab the code after auth
Show-OAuthWindow -URL $url
$regex = '(code=)(.*)$'
$code = ($uri | Select-string -pattern $regex).Matches[0].Groups[2].Value
Write-output "Received an accessCode: $code"
if (-Not [string]::IsNullOrEmpty($code)) {
$parameters = #{ }
$parameters.Add("code", $code)
# NOTE: errors ignored as this appears to error due to a null response
#confirm the consent code
Invoke-AzureRmResourceAction -Action "confirmConsentCode" -ResourceId $connection.ResourceId -Parameters $parameters -Force -ErrorAction Ignore
}
#retrieve the connection
$connection = Get-AzureRmResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $ResourceGroupName -ResourceName $ConnectionName
Write-Host "connection status now: " $connection.Properties.Statuses[0]
Reference:
Deploy Logic Apps & API Connection with ARM ยท in my room (bruttin.com)

Create Resource Group and Deploy Resource

According to the Microsoft Documentation, it is now possible to create Resource Groups and deploy resources to the newly created resource group. There is a small catch though, at the very beginning, we have this disclaimer -
Subscription level deployment is different from resource group deployment in the following aspects:
Schema and commands
The schema and commands you use for subscription-level deployments are different than resource group deployments.
For the schema, use https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#
This throws a major issue, the azuredeploy.json is no longer recognized as the deployment template as it is not using the resource deployment schema (https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#).
So, the other option was to create the Resource Group as a Nested Template and put a dependsOn for the child resources that will be created, this now allowed me to Deploy/Validate the file. However, this possesses a new issue. Even though a dependsOn dictates that the resource group is created, it still fails to recognize this and comes back with an error - resource group could not be found, hence the resources could not be deployed. I tried using a Linked Template (I know this does not make any difference, but still)
Anyone, managed to do this by any chance?
Created Resource Groups and deployed resources at the same time.
Overcome the hurdle of trying to use DependsOn and still not get the right deployment or validation?
Adding my code.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "North Europe"
},
"FirstResourceGroupName": {
"type": "string",
"defaultValue": "myFirstRG"
},
"FirstBlobStorageName": {
"type": "string",
"defaultValue": "North Europe"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "ResourceGroupDeployment",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.1",
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[parameters('location')]",
"name": "[parameters('FirstResourceGroupName')]",
"properties": {}
}
],
"outputs" : {}
}
}
},
{
//ResourceDeployment
"type": "Microsoft.Resources/deployments",
"name": "StorageDeployment",
"apiVersion": "2017-05-10",
"dependsOn": [
"[concat('Microsoft.Resources/deployments/', 'ResourceGroupDeployment')]"
//"ResourceGroupDeployment"
],
"resourceGroup": "[parameters('FirstResourceGroupName')]",
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2017-10-01",
"name": "[parameters('FirstBlobStorageName')]",
"location": "[parameters('location')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS"
}
}
],
"outputs": {}
}
}
}
],
"outputs": {}
}
The solution proposed by MS is good, when you are not using Visual Studio or Portal for deployment. My major issue was the validation of the template which again will not work for subscription level deployment, as it uses a schema that is not recogonised as an ARM.
It may work as #4c74356b41 suggested through any other means i.e. cli\sdks\rest api, but I did not go down that path.
The other solution I had was to run a powershell script by adding a step on the Azure DevOps pipeline. Which was the closest I came to making this work, but again the validation to check if my deployment would succeed, still was up in the air. I did not want my release pipeline to fail because of an invalid template.
Here is what I have gathered, the reason why the validation failed (even with deploying the RG and using a dependsOn) was because the Resource Groups will not be created until you deploy the template. The template deployment will not happen unless it passes validation as the Resource Groups does not exist. So we are stuck in a loop. The two options are either create them manually on the portal before validating (this defies the point of automation) or use a simple powershell step before validating them. The latter is what I have gone with. I know this is unorthodoxed, but works.... and also validates my template.
NOTE - The solution is different from the original problem, as I have used multiple resource group creation. According to MS documentation, you can have up to 5 RG deployed this way.
First, create a resource group file that will hold the resource groups you'd want to create. It will be just a simple JSON file like,
{
"rgNames":
{
"rg1": { "rg": "resource-group-main" },
"rg2": { "rg": "resource-group-backup" }
}
}
Use the same values you have added to this file as a parameter, so you can use them to deploy resources to.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"ResourceGroups": {
"type": "object",
//If you are changing this value !!!! Please make sure you are also updating the same in the ResourceGroups.ARM.json !!!!
"allowedValues": [
{
"rgNames":
{
"rg1": { "rg": "resource-group-main" },
"rg2": { "rg": "resource-group-backup" }
}
}
]
}
}
Second, change the PS script to include the code where it will loop through the list of resource groups it need to deploy.
# Set '$RGTemplateFile' parameter to be the name of the file you added to your project
$rgFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $RGTemplateFile))
$rgString = Get-Content -Raw -Path $rgFile | ConvertFrom-Json
# helper to turn PSCustomObject into a list of key/value pairs
function Get-ObjectMembers {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[PSCustomObject]$obj
)
$obj | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]#{Key = $key; Value = $obj."$key"}
}
}
$rgValues = $jsonParam | Get-ObjectMembers | foreach {
$_.Value | Get-ObjectMembers | foreach {
[PSCustomObject]#{
RGName = $_.value.rgNames | select -First 1
}
}
}
foreach ($values in $rgValues)
{
New-AzureRmResourceGroup -Name $values.RGName -Location $ResourceGroupLocation -Verbose -Force
}
add the above code, just before where it performs a validation -
if ($ValidateOnly) {
$ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
#OptionalParameters)
:
Finally, change the deployment template file (azuredeploy.json) to do either a nested template deployment or a linked template to deploy resources on the RG you have declared.(I have used Linked, as it looks neater)
"variables": {
"rg1Name": "[parameters('ResourceGroups')['rgNames']['rg1'].rg]",
"rg2Name": "[parameters('ResourceGroups')['rgNames']['rg2'].rg]",
"blob1Name": "[parameters('blob1')]",
"blob2Name": "[parameters('blob2')]",
"arm1": "[concat(parameters('_artifactsLocation'), 'rg1/rg1.ARM.json', parameters('_artifactsLocationSasToken'))]",
"arm2": "[concat(parameters('_artifactsLocation'), 'rg2/rg2.ARM.json', parameters('_artifactsLocationSasToken'))]"
},
"resources": [
{
//RG1 Resources Deployment
"type": "Microsoft.Resources/deployments",
"name": "RG1Resources",
"apiVersion": "2017-05-10",
"resourceGroup": "[variables('rg1Name')]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('arm1')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"blob1Name": {
"value": "[variables('blob1Name')]"
}
}
}
},
{
//RG2 Resources Deployment
"type": "Microsoft.Resources/deployments",
"name": "RG2Resources",
"apiVersion": "2017-05-10",
"resourceGroup": "[variables('rg2Name')]",
"properties": {
"mode": "Incremental",
"templateLink": {
"uri": "[variables('arm2')]",
"contentVersion": "1.0.0.0"
},
"parameters": {
"blobName": {
"value": "[variables('blob2Name')]"
}
}
}
}
],
"outputs": {}
}
Your rg1.ARM.json and rg2.ARM.json files looks like, obviously one could have more than one resource.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"blobName": {
"type": "string"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('blobName')]",
"kind": "StorageV2",
"apiVersion": "2018-07-01",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"properties": {}
}
],
"outputs": {
}
}
Once this is set up, you will be able to validate the file as the PS script will create the RG's for you before it passes through validation.
Example taken from official documentation:
{
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
"contentVersion": "1.0.0.1",
"parameters": {
"rgName": {
"type": "string"
},
"rgLocation": {
"type": "string"
},
"storagePrefix": {
"type": "string",
"maxLength": 11
}
},
"variables": {
"storageName": "[concat(parameters('storagePrefix'), uniqueString(subscription().id, parameters('rgName')))]"
},
"resources": [
{
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[parameters('rgLocation')]",
"name": "[parameters('rgName')]",
"properties": {}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "storageDeployment",
"resourceGroup": "[parameters('rgName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups/', parameters('rgName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2017-10-01",
"name": "[variables('storageName')]",
"location": "[parameters('rgLocation')]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS"
}
}
]
}
}
}
]
}
does exactly what you need.
https://learn.microsoft.com/en-us/azure/azure-resource-manager/deploy-to-subscription#create-resource-group-and-deploy-resources

Azure custom extension fails with Error message:"Finished executing command"

I am starting to test custom extensions in Azure and started with very simple json and ps1 script. My json file looks like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string"
}
},
"resources": [
{
"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('vmName'),'/RunDateTimeScript')]",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"properties": {
"publisher": "Microsoft.Compute",
"type": "CustomScriptExtension",
"typeHandlerVersion": "1.8",
"autoUpgradeMinorVersion": true,
"settings": {
"fileUris": [
"https://github.com/username/repo/blob/master/writedatetime.ps1"
],
"commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -File writedatetime.ps1"
}
}
}
]
}
And my powershell script is a oneliner:
(Get-Date).DateTime | Out-File "C:\Testing.txt" -Append
However, when I run the usual powershell deployment command:
New-AzureRmResourceGroupDeployment -ResourceGroupName MyResources -TemplateFile .\extensionexample.json -vmName MyVmName01
I get failed as a result and:
"message": "VM has reported a failure when processing extension
'RunDateTimeScript'. Error message: \"Finished executing command\"."
So what I am doing wrong and how to fix this?
The problem with this was pretty dumb. I used the wrong url for github as it should be the raw instead of the one in template. i.e https://raw.githubusercontent.com/username/repo/master/script.ps1

ARM Template complex parameters from Runbook Workflow

Have a question about ARM template deployment, specifically calling that deployment from Runbook Powershell workflow using New-AzureRmResourceGroupDeployment cmdlet.
I am trying to use dynamic copy loop in in doing so I am using following formatted parameter in the template:
"aseApAppSettings": {
"type": "object",
"defaultValue": {
"apps": [
{
"name": "app-api-ecom",
"kind": "api"
},
{
"name": "app-ecom",
"kind": "web"
}
]
}
},
Then I create resources based on that:
{
"type": "Microsoft.Web/sites",
"kind": "[parameters('aseApAppSettings').apps[copyIndex()].kind]",
"name": "[concat(parameters('aseApName'),'sv-',parameters('aseApAppSettings').apps[copyIndex()].name)]",
"apiVersion": "2016-08-01",
"location": "East US 2",
"scale": null,
"properties": {...
},
"copy": {
"name": "svLoop",
"count": "[length(parameters('aseApAppSettings').apps)]"
},
"dependsOn": []
},
All works when template is deployed through Template Deployment
I need to call for this deployment from Powershell Workflow runbook and having tough time defining the parameter
I've tried setting it as
{"apps":[{"name":"falcon-api-ecom","kind":"api"},{"name":"falcon-ecom","kind":"web"}]}
during test but it fails with message "Cannot find parameter"
So I have tried using ConvertFrom-Json
But it sends this to my template
"CliXml": "<Objs Version=\"1.1.0.1\"
xmlns=\"http://schemas.microsoft.com/powers...
Please help,
Thanks
Sample from Runbook
workflow Build-Ase {
param
(
#Environment Parameters
[Parameter(Mandatory = $true)]
[object]
$aseApAppSettings
)
$params = #{
"aseApAppSettings" = $aseApAppSettings;
}
$job = New-AzureRmResourceGroupDeployment -ResourceGroupName $vnetRGName -TemplateUri $templateParameterUri -TemplateParameterObject $params
Write-Output $job
Nested objects didn't work for me either, but passing them in as a json string combined with the json function did work for me
Deployment script
$addionalParameters = New-Object -TypeName Hashtable
$addionalParameter1 = "{ ""prop1"": [ { ""name"": ""a"", ""value"": ""1"" }, { ""name"": ""b"", ""value"": ""2"" } ], ""prop2"": { ""name"": ""c"", ""value"": ""3"" } }"
$addionalParameters["myComplexNestedOnjectAsJsonString"] = $addionalParameter1
$deploymentOutput = New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -Name $deploymentName -TemplateFile $templateFilePath `
-TemplateParameterFile $parametersFilePath #addionalParameters
Template
{
"parameters": {
"myComplexNestedObjectAsJsonString": {
"type": "string"
}
},
"variables": {
"myComplexNestedObject" : "[json(parameters('myComplexNestedObjectAsJsonString'))]"
},
"resources": [],
"outputs": {
"prop1A": {
"type": "string",
"value": "[variables('myComplexNestedObject').prop1[0].value]"
},
"prop2": {
"type": "string",
"value": "[variables('myComplexNestedObject').prop2.value]"
}
}
}
Try using splatting. For me its the only thing that works with complex nested parameter objects. Also note how the aseApAppSettings parameter is constructed.
$params = #{
$aseApAppSettings = #{ #( {name=...;kind=...},{...},...,{...} ) }
}
New-AzureRmResourceGroupDeployment -ResourceGroupName $vnetRGName -TemplateUri $templateParameterUri #params
ps. ... represent placeholders

Resources