<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://blog.aabech.no/rss/xslt"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Lars-Erik's blog</title>
    <link>https://blog.aabech.no/</link>
    <description>Ramblings about Umbraco, .net and JavaScript development. With a sprinkle of other stuff.</description>
    <generator>Articulate, blogging built on Umbraco</generator>
    <item>
      <guid isPermaLink="false">1162</guid>
      <link>https://blog.aabech.no/archive/armlinker-100-released/</link>
      <category>automation</category>
      <category>azure</category>
      <title>ARMLinker 1.0.0 released</title>
      <description>&lt;h2&gt;ARM What?&lt;/h2&gt;
&lt;p&gt;I've been having fun with ARM Templates the last couple of months.
It's a wonderful way to keep your Azure Resource definitions in source control.
Not to mention being able to parameterize deployment to different environments,
and not least keeping your secrets neatly tucked away in a vault.&lt;/p&gt;
&lt;p&gt;However, compiling a set of resources from multiple files currently requires
you to put your templates online. I want to keep most of our customer products'
templates private, and to do that one have to &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#linked-template"&gt;jump through hoops&lt;/a&gt; to copy the
files over to a storage account and link to the given URLs.
It kind of defeats the whole purpose for me.&lt;/p&gt;
&lt;p&gt;So I went and created a small tool to be able to link templates locally.&lt;/p&gt;
&lt;h2&gt;How to use it&lt;/h2&gt;
&lt;p&gt;There's an installable project type for Visual Studio called &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/create-visual-studio-deployment-project"&gt;&amp;quot;Azure Resource Group&amp;quot;&lt;/a&gt;.
When you create one you get a few files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy-AzureResourceGroup.ps1&lt;/li&gt;
&lt;li&gt;azuredeploy.json&lt;/li&gt;
&lt;li&gt;azuredeploy.parameters.json&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can stuff all of the resources you require in the azuredeploy.json file, and finally deploy them using a wizard, or run the PowerShell script in a CD pipeline.&lt;/p&gt;
&lt;p&gt;By installing ARMLinker you can start running the tool to link other JSON files
into the main azuredeploy.json file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;install-module ARMLinker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's say we have a Logic App (what I've been doing).&lt;br /&gt;
To deploy it and its connections and other needed resources, we often want
a bounch of secret keys for different APIs and such.&lt;/p&gt;
&lt;p&gt;Here's a trimmed down sample of a Logic App that runs a SQL command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &amp;quot;$schema&amp;quot;: &amp;quot;https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#&amp;quot;,
    &amp;quot;contentVersion&amp;quot;: &amp;quot;1.0.0.0&amp;quot;,
    &amp;quot;parameters&amp;quot;: {
        &amp;quot;Tags&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
            &amp;quot;defaultValue&amp;quot;: {
                &amp;quot;Customer&amp;quot;: &amp;quot;My customer&amp;quot;,
                &amp;quot;Product&amp;quot;: &amp;quot;Their Logic App&amp;quot;,
                &amp;quot;Environment&amp;quot;: &amp;quot;Beta&amp;quot;
            }
        },
        &amp;quot;SQL-Server&amp;quot;: {
            &amp;quot;defaultValue&amp;quot;: &amp;quot;some.database.windows.net&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
        },
        &amp;quot;SQL-User&amp;quot;: {
            &amp;quot;defaultValue&amp;quot;: &amp;quot;appuser&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
        },
        &amp;quot;SQL-Password&amp;quot;: {
            &amp;quot;defaultValue&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;securestring&amp;quot;
        },
        &amp;quot;SQL-Database-Name&amp;quot;: {
            &amp;quot;defaultValue&amp;quot;: &amp;quot;beta-database&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
        }
    },
    &amp;quot;variables&amp;quot;: {
        &amp;quot;ConnectionName&amp;quot;: &amp;quot;[replace(concat(parameters('Tags').Customer, '-', parameters('Tags').Product, '-SQLConnection-', parameters('Tags').Environment), ' ', '')]&amp;quot;,
        &amp;quot;LogicAppName&amp;quot;: &amp;quot;[replace(concat(parameters('Tags').Customer, '-', parameters('Tags').Product, '-', parameters('Tags').Environment), ' ', '')]&amp;quot;
    },
    &amp;quot;resources&amp;quot;: [
        {
            &amp;quot;type&amp;quot;: &amp;quot;Microsoft.Web/connections&amp;quot;,
            &amp;quot;apiVersion&amp;quot;: &amp;quot;2016-06-01&amp;quot;,
            &amp;quot;location&amp;quot;: &amp;quot;westeurope&amp;quot;,
            &amp;quot;name&amp;quot;: &amp;quot;[variables('ConnectionName')]&amp;quot;,
            &amp;quot;properties&amp;quot;: {
                &amp;quot;api&amp;quot;: {
                    &amp;quot;id&amp;quot;: &amp;quot;[concat(subscription().id,'/providers/Microsoft.Web/locations/westeurope/managedApis/sql')]&amp;quot;
                },
                &amp;quot;displayName&amp;quot;: &amp;quot;sql_connection&amp;quot;,
                &amp;quot;parameterValues&amp;quot;: {
                    &amp;quot;server&amp;quot;: &amp;quot;[parameters('SQL-Server')]&amp;quot;,
                    &amp;quot;database&amp;quot;: &amp;quot;[parameters('SQL-Database-Name')]&amp;quot;,
                    &amp;quot;authType&amp;quot;: &amp;quot;windows&amp;quot;,
                    &amp;quot;username&amp;quot;: &amp;quot;[parameters('SQL-User')]&amp;quot;,
                    &amp;quot;password&amp;quot;: &amp;quot;[parameters('SQL-Password')]&amp;quot;
                }
            }
        }, 
        {
            &amp;quot;type&amp;quot;: &amp;quot;Microsoft.Logic/workflows&amp;quot;,
            &amp;quot;apiVersion&amp;quot;: &amp;quot;2017-07-01&amp;quot;,
            &amp;quot;name&amp;quot;: &amp;quot;[variables('LogicAppName')]&amp;quot;,
            &amp;quot;dependsOn&amp;quot;: [ &amp;quot;[resourceId('Microsoft.Web/connections', variables('ConnectionName'))]&amp;quot; ], 
            &amp;quot;location&amp;quot;: &amp;quot;westeurope&amp;quot;,
            &amp;quot;properties&amp;quot;: {
                &amp;quot;state&amp;quot;: &amp;quot;Enabled&amp;quot;,
                &amp;quot;definition&amp;quot;: {
                    &amp;quot;$schema&amp;quot;: &amp;quot;https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#&amp;quot;,
                    &amp;quot;contentVersion&amp;quot;: &amp;quot;1.0.0.0&amp;quot;,
                    &amp;quot;parameters&amp;quot;: {
                        &amp;quot;$connections&amp;quot;: {
                            &amp;quot;defaultValue&amp;quot;: {},
                            &amp;quot;type&amp;quot;: &amp;quot;Object&amp;quot;
                        },
                        &amp;quot;SQL-Server&amp;quot;: {
                            &amp;quot;defaultValue&amp;quot;: &amp;quot;&amp;quot;,
                            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;
                        },
                        &amp;quot;SQL-Database-Name&amp;quot;: {
                            &amp;quot;defaultValue&amp;quot;: &amp;quot;&amp;quot;,
                            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot; 
                        }
                    },
                    &amp;quot;triggers&amp;quot;: {
                        &amp;quot;Recurrence&amp;quot;: {
                            &amp;quot;recurrence&amp;quot;: {
                                &amp;quot;frequency&amp;quot;: &amp;quot;Day&amp;quot;,
                                &amp;quot;interval&amp;quot;: 1
                            },
                            &amp;quot;type&amp;quot;: &amp;quot;Recurrence&amp;quot;
                        }
                    },
                    &amp;quot;actions&amp;quot;: {
                        &amp;quot;Execute_a_SQL_query_(V2)&amp;quot;: {
                            &amp;quot;runAfter&amp;quot;: {},
                            &amp;quot;type&amp;quot;: &amp;quot;ApiConnection&amp;quot;,
                            &amp;quot;inputs&amp;quot;: {
                                &amp;quot;body&amp;quot;: {
                                    &amp;quot;query&amp;quot;: &amp;quot;select 'do something really useful' as task&amp;quot;
                                },
                                &amp;quot;host&amp;quot;: {
                                    &amp;quot;connection&amp;quot;: {
                                        &amp;quot;name&amp;quot;: &amp;quot;@parameters('$connections')['sql']['connectionId']&amp;quot;
                                    }
                                },
                                &amp;quot;method&amp;quot;: &amp;quot;post&amp;quot;,
                                &amp;quot;path&amp;quot;: &amp;quot;/v2/datasets/@{encodeURIComponent(encodeURIComponent(parameters('SQL-Server')))},@{encodeURIComponent(encodeURIComponent(parameters('SQL-Database-Name')))}/query/sql&amp;quot;
                            }
                        }
                    },
                    &amp;quot;outputs&amp;quot;: {}
                },
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;$connections&amp;quot;: {
                        &amp;quot;value&amp;quot;: {
                            &amp;quot;sql&amp;quot;: {
                                &amp;quot;connectionId&amp;quot;: &amp;quot;[resourceId('Microsoft.Web/connections', variables('ConnectionName'))]&amp;quot;,
                                &amp;quot;connectionName&amp;quot;: &amp;quot;variables('ConnectionName')&amp;quot;,
                                &amp;quot;id&amp;quot;: &amp;quot;/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Web/locations/westeurope/managedApis/sql&amp;quot;
                            }
                        }
                    },
                    &amp;quot;SQL-Server&amp;quot;: {
                        &amp;quot;value&amp;quot;: &amp;quot;[parameters('SQL-Server')]&amp;quot;
                    },
                    &amp;quot;SQL-Database-Name&amp;quot;: {
                        &amp;quot;value&amp;quot;: &amp;quot;[parameters('SQL-Database-Name')]&amp;quot;
                    }
                }
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The parameters here are ARM template parameters. The most interesting one is the secret password for the database server. It's secret, so it's not supposed to live in our parameter file or source control. We've also got the ID of the connection, which will be the &lt;em&gt;real&lt;/em&gt; ID in the actual deployed Logic App.&lt;/p&gt;
&lt;p&gt;There's a fancy way to go about keeping the password in a key vault on Azure, and the Visual Studio Wizard is really helpful with putting it into a vault.&lt;/p&gt;
&lt;p&gt;When we're done and ready for production, a parameter file may look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &amp;quot;$schema&amp;quot;: &amp;quot;https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#&amp;quot;,
    &amp;quot;contentVersion&amp;quot;: &amp;quot;1.0.0.0&amp;quot;,
    &amp;quot;parameters&amp;quot;: {
        &amp;quot;Tags&amp;quot;: {
            &amp;quot;value&amp;quot;: {
                &amp;quot;Customer&amp;quot;: &amp;quot;My customer&amp;quot;,
                &amp;quot;Product&amp;quot;: &amp;quot;Their Logic App&amp;quot;,
                &amp;quot;Environment&amp;quot;: &amp;quot;Production&amp;quot;
            }
        },
        &amp;quot;SQL-Database-Name&amp;quot;: {
            &amp;quot;value&amp;quot;: &amp;quot;production-database&amp;quot;
        },
        &amp;quot;SQL-Password&amp;quot;: {
            &amp;quot;reference&amp;quot;: {
                &amp;quot;keyVault&amp;quot;: {
                    &amp;quot;id&amp;quot;: &amp;quot;/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/Vault-Group/providers/Microsoft.KeyVault/vaults/OurKeyVault&amp;quot;
                },
                &amp;quot;secretName&amp;quot;: &amp;quot;CustomerSQLPassword&amp;quot;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One of the beauties of using Logic Apps is that it have this nice GUI to work with in the portal. There's also an extension for Visual Studio to be able to edit them within Visual Studio.&lt;/p&gt;
&lt;p&gt;However, the definition will look like this when viewed with the code editor. (I removed the bulk of it, but notice the parameters) &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &amp;quot;definition&amp;quot;: {
        &amp;quot;$schema&amp;quot;: &amp;quot;https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#&amp;quot;,
        &amp;quot;actions&amp;quot;: {
            &amp;quot;Execute_a_SQL_query_(V2)&amp;quot;: {
                &amp;quot;inputs&amp;quot;: {
                    &amp;quot;body&amp;quot;: {
                        &amp;quot;query&amp;quot;: &amp;quot;select 'do something really useful' as task&amp;quot;
                    },
                    &amp;quot;host&amp;quot;: {
                        &amp;quot;...&amp;quot;
                    },
                    &amp;quot;...&amp;quot;
                },
                &amp;quot;runAfter&amp;quot;: {},
                &amp;quot;type&amp;quot;: &amp;quot;ApiConnection&amp;quot;
            }
        },
        &amp;quot;...&amp;quot;,
        &amp;quot;parameters&amp;quot;: {
            &amp;quot;$connections&amp;quot;: {
                &amp;quot;defaultValue&amp;quot;: {},
                &amp;quot;type&amp;quot;: &amp;quot;Object&amp;quot;
            },
            &amp;quot;...&amp;quot;
        },
        &amp;quot;triggers&amp;quot;: {
            &amp;quot;...&amp;quot;
        }
    },
    &amp;quot;parameters&amp;quot;: {
        &amp;quot;$connections&amp;quot;: {
            &amp;quot;value&amp;quot;: {
                &amp;quot;sql&amp;quot;: {
                    &amp;quot;connectionId&amp;quot;: &amp;quot;/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/CustomerResourceGroup/providers/Microsoft.Web/connections/MyCustomer-TheirProduct-SQLConnection-Prod&amp;quot;,
                    &amp;quot;connectionName&amp;quot;: &amp;quot;MyCustomer-TheirProduct-SQLConnection-Prod&amp;quot;,
                    &amp;quot;id&amp;quot;: &amp;quot;/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Web/locations/westeurope/managedApis/sql&amp;quot;
                }
            }
        },
        &amp;quot;SQL-Database-Name&amp;quot;: {
            &amp;quot;value&amp;quot;: &amp;quot;production-database&amp;quot;
        },
        &amp;quot;SQL-Server&amp;quot;: {
            &amp;quot;value&amp;quot;: &amp;quot;some.database.windows.net&amp;quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice that the parameters are all filled out. We can't copy this into our ARM template since it's all real Resource ID references.&lt;/p&gt;
&lt;p&gt;There's another way to get only the definition. We can use the &lt;a href="https://docs.microsoft.com/en-us/powershell/module/az.logicapp"&gt;Az.LogicApp&lt;/a&gt; powershell module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(get-azlogicapp -resourcegroupname CustomerResourceGroup -name mycustomer-theirproduct-prod).definition.ToString()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It will give us only the &lt;code&gt;definition&lt;/code&gt; part of the template.&lt;/p&gt;
&lt;p&gt;Both gives us a means to put &lt;em&gt;only&lt;/em&gt; the &lt;em&gt;definition&lt;/em&gt; of the logic app into a file in our local project.&lt;/p&gt;
&lt;p&gt;Now we can go back to the ARM template and replace the definition with a simple link to the file.
Say we &lt;code&gt;Set-Content&lt;/code&gt; the result of the statement above into a file called &amp;quot;logicapp.json&amp;quot;. We can modify the ARM template as such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &amp;quot;$schema&amp;quot;: &amp;quot;https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#&amp;quot;,
    &amp;quot;contentVersion&amp;quot;: &amp;quot;1.0.0.0&amp;quot;,
    &amp;quot;parameters&amp;quot;: {
        &amp;quot;...&amp;quot;
    },
    &amp;quot;variables&amp;quot;: {
        &amp;quot;...&amp;quot;
    },
    &amp;quot;resources&amp;quot;: [
        {
            &amp;quot;type&amp;quot;: &amp;quot;Microsoft.Web/connections&amp;quot;,
            &amp;quot;...&amp;quot;
        }, 
        {
            &amp;quot;type&amp;quot;: &amp;quot;Microsoft.Logic/workflows&amp;quot;,
            &amp;quot;apiVersion&amp;quot;: &amp;quot;2017-07-01&amp;quot;,
            &amp;quot;name&amp;quot;: &amp;quot;[variables('LogicAppName')]&amp;quot;,
            &amp;quot;dependsOn&amp;quot;: [ &amp;quot;[resourceId('Microsoft.Web/connections', variables('ConnectionName'))]&amp;quot; ], 
            &amp;quot;location&amp;quot;: &amp;quot;westeurope&amp;quot;,
            &amp;quot;properties&amp;quot;: {
                &amp;quot;state&amp;quot;: &amp;quot;Enabled&amp;quot;,
                &amp;quot;definition&amp;quot;: {
                    &amp;quot;templateLink&amp;quot; {
                        &amp;quot;uri&amp;quot;: &amp;quot;.\logicapp.json&amp;quot;
                    }
                },
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;$connections&amp;quot;: {
                        &amp;quot;value&amp;quot;: {
                            &amp;quot;sql&amp;quot;: {
                                &amp;quot;connectionId&amp;quot;: &amp;quot;[resourceId('Microsoft.Web/connections', variables('ConnectionName'))]&amp;quot;,
                            &amp;quot;connectionName&amp;quot;: &amp;quot;variables('ConnectionName')&amp;quot;,
                                &amp;quot;id&amp;quot;: &amp;quot;/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Web/locations/westeurope/managedApis/sql&amp;quot;
                            }
                        }
                    },
                    &amp;quot;...&amp;quot;
                }
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By running &lt;code&gt;ARMLinker&lt;/code&gt; we will have the same generated file as we started with,
but we can use the GUI for the logic app and easily fetch the new JSON for it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Convert-TemplateLinks azuredeploy.json azuredeploy.linked.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For now, I've actually turned those around and put the &amp;quot;linked&amp;quot; template in a file called azuredeploy.linked.json in order to generate the &amp;quot;conventional&amp;quot; azuredeploy.json file.&lt;/p&gt;
&lt;h2&gt;More options&lt;/h2&gt;
&lt;p&gt;When using the &amp;quot;copy content from the editor&amp;quot; method mentioned above, we have to make sure to copy &lt;em&gt;only&lt;/em&gt; the definition object. Otherwise we'll bring the concrete parameters into the local file.&lt;/p&gt;
&lt;p&gt;Do not despair!&lt;/p&gt;
&lt;p&gt;There's another option that doesn't match the official schema for &amp;quot;templateLink&amp;quot;.
By adding a property called &amp;quot;jsonPath&amp;quot; we can point to an object deeper in the linked file.
Say we copy the content from the online editor.&lt;/p&gt;
&lt;p&gt;We can modify the linked template as such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;definition&amp;quot;: {
    &amp;quot;templateLink&amp;quot; {
        &amp;quot;uri&amp;quot;: &amp;quot;.\logicapp.json&amp;quot;,
        &amp;quot;jsonPath&amp;quot;: &amp;quot;definition&amp;quot;
    }
},
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It will now only merge the definition part from the logicapp.json file.&lt;/p&gt;
&lt;p&gt;I only implemented dot separated paths for now, so exotic paths to arrays or paths with special characters won't work.&lt;/p&gt;
&lt;p&gt;IE. &lt;code&gt;resources[0]['very fancy'].thing&lt;/code&gt; won't work, but &lt;code&gt;things.with.dots&lt;/code&gt; will work.&lt;/p&gt;
&lt;h2&gt;Plans and dreams&lt;/h2&gt;
&lt;p&gt;This is pretty much only a workaround while waiting for Microsoft to realise this is totally useful and obvious.&lt;/p&gt;
&lt;p&gt;I originally intended it to be a Custom Tool for Visual Studio, but I could not for the life of me figure out how to enable Custom Tools in projects not of the C# or Visual Basic archetypes.&lt;/p&gt;
&lt;p&gt;If anyone picks up on it, I'll happily discuss new features and even happierly receive meaningful pull requests.&lt;/p&gt;
&lt;p&gt;Other than that, I believe it does the job properly. It can be used in CD pipelines. It should even work for any JSON, not necessarily ARM templates. &lt;/p&gt;
&lt;p&gt;I would really appreciate your feedback, and hope you like it!&lt;/p&gt;
&lt;p&gt;Now go commit and deploy something automagically while fetching coffee! 🤘😁🦄&lt;/p&gt;
&lt;h2&gt;Code and gallery links&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/lars-erik/ARMLinker"&gt;Github repository&lt;/a&gt;&lt;br /&gt;
&lt;a href="https://www.powershellgallery.com/packages/ARMLinker/1.0.1"&gt;PowerShell gallery&lt;/a&gt;&lt;/p&gt;
</description>
      <pubDate>Wed, 22 Jan 2020 23:22:41 Z</pubDate>
      <a10:updated>2020-01-22T23:22:41Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">1136</guid>
      <link>https://blog.aabech.no/archive/automating-umbraco-with-powershell/</link>
      <category>umbraco</category>
      <category>automation</category>
      <title>Automating Umbraco with PowerShell</title>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;This particular example is for warming up a UCommerce site in staging, but the technique can be used for anything you can do in the Umbraco backoffice. Especially with the new REST APIs coming out.&lt;/p&gt;
&lt;p&gt;In this particular case, I've sinned and not created a good test environment for the last few integration bits of a project. It was hard to tune the production behavior of some code without actually running it in production. However, it's running Umbraco 7.5.13 and UCommerce 7.7. It's probably missing the other performance fix too, but the result is that it takes quite a while to warm up everything. So we warm it up in staging and then swap slots to get it fresh, awake and blazing fast into production. &lt;/p&gt;
&lt;h2&gt;Resolve&lt;/h2&gt;
&lt;p&gt;After having done this a few times, I figured I wanted something to do while waiting. What better activity than automating the whole routine so I could do something else instead? (Like automating automation...) Here's a powershell script I ended up with to warm up everything in the backoffice. I'll go through the pieces below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Param(
    [string]$password
)

$ws = New-Object Microsoft.PowerShell.Commands.WebRequestSession

$body = @{
    &amp;quot;username&amp;quot;=&amp;quot;admin@admin.com&amp;quot;
    &amp;quot;password&amp;quot;=$password
}

$json = $body | ConvertTo-Json

Invoke-RestMethod `
    -Method Post `
    -ContentType &amp;quot;application/json&amp;quot; `
    -Uri &amp;quot;https://customer-staging.azurewebsites.net/umbraco/backoffice/UmbracoApi/Authentication/PostLogin&amp;quot; `
    -WebSession $ws `
    -Body $json

Write-Host &amp;quot;20%&amp;quot;

Invoke-RestMethod -Method Get -Uri &amp;quot;https://customer-staging.azurewebsites.net/umbraco/ucommerce/catalog/editcategory.aspx?id=718&amp;quot; -WebSession $ws

Write-Host &amp;quot;40%&amp;quot;

Invoke-RestMethod -Method Get -Uri &amp;quot;https://customer-staging.azurewebsites.net/umbraco/ucommerce/catalog/editproduct.aspx?id=465&amp;amp;parentcategoryId=718&amp;quot; -WebSession $ws

Write-Host &amp;quot;60%&amp;quot;
Invoke-RestMethod -Method Get -Uri &amp;quot;https://customer-staging.azurewebsites.net/umbraco/ucommerce/settings/orders/editpaymentmethod.aspx?id=8&amp;quot; -WebSession $ws

Write-Host &amp;quot;80%&amp;quot;

Invoke-RestMethod -Method Get -Uri &amp;quot;https://customer-staging.azurewebsites.net/umbraco/ucommerce/settings/orders/editshippingmethod.aspx?id=10&amp;quot; -WebSession $ws
Write-Host &amp;quot;100%&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The effect is that we log into Umbraco using a provided password, and instead of navigating and clicking everything, we fire a request triggering all the caching and JIT compilation for us. Even though I'm using the &lt;code&gt;Invoke-RestMethod&lt;/code&gt; cmdlet, I can do regular web calls. The cmdlet has a sibling called &lt;code&gt;Invoke-WebRequest&lt;/code&gt;, but the rest version is better for posting commands. It's mostly a matter of mental context, but they have a few differences.&lt;/p&gt;
&lt;h2&gt;Log into Umbraco&lt;/h2&gt;
&lt;p&gt;To set up an authorized session with Umbraco, we can call the PostLogin action. It's the same endpoint that is used from the login screen. An authorized session means that we need to get a cookie and pass it with all our requests. In order for each &lt;code&gt;Invoke-RestMethod&lt;/code&gt; to pass this cookie, we can create a &lt;code&gt;WebRequestSession&lt;/code&gt; we pass to each call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ws = New-Object Microsoft.PowerShell.Commands.WebRequestSession

Invoke-RestMethod -WebSession $ws -Uri &amp;quot;...&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If the response brings a cookie, it's kept in the &lt;code&gt;WebRequestSession&lt;/code&gt;, and subsequently passed back with each new request. Just like a browser.&lt;/p&gt;
&lt;p&gt;Then we need some JSON to pass our username and password. You can declare dictionaries of sorts in PowerShell like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$body = @{
    &amp;quot;username&amp;quot;=&amp;quot;admin@admin.com&amp;quot;
    &amp;quot;password&amp;quot;=$password
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And then convert it to JSON by piping it to the &lt;code&gt;ConvertTo-Json&lt;/code&gt; cmdlet:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$json = $body | ConvertTo-Json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally we're ready to fire the request off to Umbraco, adding config for HTTP method, ContentType etc.:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Invoke-RestMethod `
    -Method Post `
    -ContentType &amp;quot;application/json&amp;quot; `
    -Uri &amp;quot;https://customer-staging.azurewebsites.net/umbraco/backoffice/UmbracoApi/Authentication/PostLogin&amp;quot; `
    -WebSession $ws `
    -Body $json
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Automate Umbraco&lt;/h2&gt;
&lt;p&gt;For this I just needed to kick off a request to some pages, but posting messages around like rebuilding a grumpy index, running an ad hoc task, even publishing should be just as simple.&lt;/p&gt;
&lt;p&gt;It takes a while, so I added a little status message. I'm sure PowerShell wizards would pack this stuff into better reusable parts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Write-Host &amp;quot;20%&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now reuse that &lt;code&gt;WebRequestSession&lt;/code&gt; object to fire off new &lt;em&gt;authenticated&lt;/em&gt; requests:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Invoke-RestMethod -WebSession $ws -Method Get -Uri &amp;quot;https://customer-staging.azurewebsites.net/umbraco/ucommerce/catalog/editcategory.aspx?id=718&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Do it everywhere&lt;/h2&gt;
&lt;p&gt;With all the love I can give to UCommerce, I ended up naming the script &lt;code&gt;kick-ucommerce.ps1&lt;/code&gt;. It's like kicking your old belowed car to get it started, after you've polished it. Really!&lt;br /&gt;
Adding my source folder to the path environment variable makes the script available from any shell. Even the Package Manager console in Visual Studio. &lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.aabech.no/media/1034/warmup-ucommerce.png" alt="Warm up UCommerce" /&gt;&lt;/p&gt;
&lt;p&gt;Make a note that the password is a parameter. You do change it more often than you'd like to update the code right? How 'bout automating the process? ;)&lt;/p&gt;
&lt;h2&gt;Smoke test and swap the Azure slot&lt;/h2&gt;
&lt;p&gt;I'll leave the swap-slot script I run after warming up the site here too. The cool thing about warming up with the script is that it'll fail almost immediately on the Umbraco login if anything isn't like it should. So it doubles as a smoke test.&lt;/p&gt;
&lt;p&gt;When everything looks good, I can just go:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;swap-slot -from staging -to production -site customer-x
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here's the &amp;quot;simpleness&amp;quot; of that one. There are fairly good docs on all the Azure cmdlets over on Microsoft's sites. (Ask Google. ;) )&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Param(
    [string]$site,
    [string]$from,
    [string]$to
)

$subscriptionId = &amp;quot;333112F4-4483-449C-A2DA-727E8D2E428D&amp;quot;
$resourcegroupname = &amp;quot;Common-Group&amp;quot;     # Might need to be param

Login-AzureRmAccount -SubscriptionId $subscriptionId

Swap-AzureRmWebAppSlot `
    -SourceSlotName $from `
    -DestinationSlotName $to `
    -Name $site `
    -ResourceGroupName $resourcegroupname
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That's it! Now go automate something so you get more time to do fun stuff! :)&lt;/p&gt;
</description>
      <pubDate>Tue, 07 Nov 2017 19:46:14 Z</pubDate>
      <a10:updated>2017-11-07T19:46:14Z</a10:updated>
    </item>
  </channel>
</rss>