<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anton Kuryan</title>
    <description>The latest articles on DEV Community by Anton Kuryan (@akuryan).</description>
    <link>https://dev.to/akuryan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F190723%2Fec05aed0-5b02-43bf-941c-b204101e2754.jpeg</url>
      <title>DEV Community: Anton Kuryan</title>
      <link>https://dev.to/akuryan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akuryan"/>
    <language>en</language>
    <item>
      <title>Azure Web app Kudu: overcoming api/zip built-in timeout</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Wed, 15 Sep 2021 11:55:49 +0000</pubDate>
      <link>https://dev.to/akuryan/azure-web-app-kudu-overcoming-api-zip-built-in-timeout-2j3l</link>
      <guid>https://dev.to/akuryan/azure-web-app-kudu-overcoming-api-zip-built-in-timeout-2j3l</guid>
      <description>&lt;p&gt;Azure Web apps comes in with very interesting tool, called Kudu, which is web application itself, designed to service and configure your web application. It exposes several APIs to service you - for example, MsDeploy, zip, zipdeploy. In this post I wish to cover some differences between zip and zipdeploy and how I used zip API to deploy Sitecore Unicorn data.&lt;/p&gt;

&lt;p&gt;First of all, I wish to describe my problem - we have huge Sitecore 9 web application, which required to deploy 18000 Unicorn files to CM instance in scope of every release outside of web root to solve issues with long paths (we were deploying at C:\home\N directory).&lt;/p&gt;

&lt;p&gt;Initially, I tried to solve this problem with MsDeploy - it seems to be fast and reliable and it is designed to deploy you web application to remote server. It worked, but it took too much time due to retries in-between. As I discovered, deployment of 18000 tiny files would not be the best things MsDeploy is able to handle reliably. So, I started looking in other possibilities and discovered, that Kudu exposes 2 more APIs which could be used for deployment: Zip and ZipDeploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zip vs ZipDeploy
&lt;/h2&gt;

&lt;p&gt;ZipDeploy have following possibilities&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivers files ONLY to webroot (there is some magic which one could do with NPM, but I never finished this exercise)&lt;/li&gt;
&lt;li&gt;Support both sync and async behaviors (async could be required to overcome built in app services hard-coded timeout of 230 seconds per request)&lt;/li&gt;
&lt;li&gt;Smart as MsDeploy - would not update files that have not changed; would remove files which is not present in archive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zip have following possibilities&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivers files almost to any place of web app work drive&lt;/li&gt;
&lt;li&gt;Does not remove files which is not present in archive&lt;/li&gt;
&lt;li&gt;Have only sync behavior&lt;/li&gt;
&lt;li&gt;Continues execution in background even after request have timed out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following up on my explanation where I required to deploy 18000 tiny yml files for Sitecore Unicorn outside of webroot - I have been left with only option: using zip API. But, most of my tries it required more than 230 seconds to deliver files on web app, which means that I was not able to reliably find out if unzip operation have been finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;I implemented following solution - in each archive with Unicorn files I am adding to the root of archive some unique text file (I am using build number as name and content). From my observations, this file would be extracted last. So, I am feeding api/zip with my Unicorn archive and then, if it fails with timeout, I will just keep asking api/vfs (virtual file system provider) for this file. When file appears there - it means that api/zip have finished it's work and I could proceed with Unicorn import routines. I shall note, that before starting deployment I have to clean up Unicorn folder, as zip API does not remove excessive files.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This small powershell script allowed me to solve my issue with deployment&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SonarQube Community edition and PR analysis</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Tue, 08 Dec 2020 14:51:05 +0000</pubDate>
      <link>https://dev.to/akuryan/sonarqube-community-edition-and-pr-analysis-2c37</link>
      <guid>https://dev.to/akuryan/sonarqube-community-edition-and-pr-analysis-2c37</guid>
      <description>&lt;p&gt;SonarQube is awesome tool and it's Community edition (which is free) fulfills almost everything one could dream for in the world of static code analysis. But, you could not analyze PR's with it by default and have to pay for at least Developer edition. Further you could find my attempt to overcome this limitation (it fulfills MY goals and seems to be worth sharing with broader audience).&lt;/p&gt;

&lt;p&gt;When I started working with SonarQube back in version 5 and 6 there was one mode which brought me a lot of interesting discoveries and prevented my colleague developers from submitting bad code - preview. In this mode, SonarQube does not stores data on server, but executes analysis and sends results back - and in conjunction with PR decoration plugin it was very useful. But, time goes by and version 7 removed preview mode from Community edition.&lt;/p&gt;

&lt;p&gt;So, I tried to mimic this, using a separate project on SonarQube server and created powershell script, which will retrieve quality gate status from current analysis and create bugs in Jira and attach them to some existing issue (actually, that's the added value of this script, which was my target, opposing to default CI server SonarQube runners, which would just fail your build).&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Script seems to be heavily commented and speaks by itself. Feel free to adopt it for your own usage pattern, if you will ever need it.&lt;/p&gt;

&lt;p&gt;I shall note that Developers edition of SonarQube solves the same problem much better - it have PR analysis and built-in PR decorator, but it costs money, while Community edition brings things in for free.&lt;/p&gt;

</description>
      <category>sonarqube</category>
      <category>powershell</category>
    </item>
    <item>
      <title>Transforming deployed configuration file in Azure web app</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Mon, 21 Oct 2019 07:03:35 +0000</pubDate>
      <link>https://dev.to/akuryan/transforming-deployed-configuration-file-in-azure-web-app-2mc9</link>
      <guid>https://dev.to/akuryan/transforming-deployed-configuration-file-in-azure-web-app-2mc9</guid>
      <description>&lt;p&gt;While this guide is primarily targeted towards usage with Azure Web apps and Sitecore ASP.NET solution deployed on them, still it is possible to use it on on-premise solution with other types on ASP.NET projects, running on Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reasoning behind this
&lt;/h2&gt;

&lt;p&gt;Why I actually started building all this thing up, ignoring interesting Helix publishing pipeline or dropping idea of storing source and transformation files in my source control?&lt;/p&gt;

&lt;p&gt;In Helix publishing pipeline I do not like the fact that I could not use different transformation based on roles, where I am deploying (if it is there - can you point it to me?).&lt;/p&gt;

&lt;p&gt;When storing vanilla config in source control - I do not like the fact that I will need to update it when I am updating Sitecore version (hence, merging and solving possible conflicts).&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;So, I decided to build this transformation thing anew, almost from scratch. I used the same idea as used in Helix publishing pipeline: store only transformation files under source control, as, in general, they are generic. Also, I agreed on following naming convention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configFileName.config.xdt - transforms config file with name configFileName.config lying at this path for all environments&lt;/li&gt;
&lt;li&gt;configFileName.config.xdt.roleName - transforms config file with name configFileName.config lying at this path when deploying this particular role&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;That's is a script, which could be uploaded to web app and executed via Kudu. Script wants several parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;folderWithTransforms - (optional) folder, where all transform files are residing; if not specified - script folder is taken&lt;/li&gt;
&lt;li&gt;webRoot - (optional) folder, where webRoot could be found; if not specified, folder 2 levels upper is taken&lt;/li&gt;
&lt;li&gt;roleName - optional, if there is a specific tranformations for this particular role&lt;/li&gt;
&lt;li&gt;transformationAssemblyPath - if Microsoft.Web.XmlTransform.dll is not at the same path as script&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>новости</category>
    </item>
    <item>
      <title>Road to precompiled web application based on Umbraco CMS</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Mon, 19 Aug 2019 09:25:33 +0000</pubDate>
      <link>https://dev.to/akuryan/road-to-precompiled-web-application-based-on-umbraco-cms-2fp8</link>
      <guid>https://dev.to/akuryan/road-to-precompiled-web-application-based-on-umbraco-cms-2fp8</guid>
      <description>&lt;p&gt;This is crosspost from &lt;a href="https://dobryak.org/road-to-precompiled-web-application-based-on-umbraco-cms/"&gt;my blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the journey
&lt;/h2&gt;

&lt;p&gt;Some time ago, my colleague Jeroen asked me to optimize the local development process for one of our web applications, which has more than 450 C# MVC views, which leads to a problem – extremely long local startup times (can be more than 5 minutes on decent developer workstation). Also, we’ve experienced the same problems on production, which is delivered by means of MsDeploy (though they are not so big, cause delivery to production is much rarer event than local compilation). So, I set sails on investigation and optimizing this&lt;/p&gt;

&lt;h2&gt;
  
  
  First try: MvcBuildViews
&lt;/h2&gt;

&lt;p&gt;In each C# MVC project there is an option to compile MVC views, by turning parameter MvcBuildViews to True. I supposed that this will solve our issue by compiling views to IL (Intermediate Language), but this was not the case. As I understand, the main target of this parameter is to check views for errors, so we can avoid them during runtime compilation in future – it starts aspnet_compiler.exe to compile all views to IL (this is what seems to be the goal), but, it keeps views itself unmodified and compiles to a temporary folder. So, on the next startup of web application, when a view is requested, it will be loaded unmodified from the Views folder and undergo the whole runtime compilation again, outputting IL to another temporary folder. Additional catch is: MvcBuildViews is not compatible with MsDeploy, so, if you are compiling and deploying in one run (e.g. parameter DeployOnBuild is set to True) – the build will fail, as ASPCONFIG is failing on parsing application web.config. which fails a build. However, if you do wish to have your views to be checked for compilation sanity during deployment builds, this can be fixed by modifying build process (for example, in csproj file of your MVC application) by adding the following at the end (before closing tag):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- MvcBuildViews is not compatible with DeployOnBuild - so we need to change order of build events --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MvcBuildViews&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(MvcBuildViews) == ''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/MvcBuildViews&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MSDeployPublishDependsOn&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(DeployOnBuild) And '$(MSDeployPublishDependsOn)'!=''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;MvcBuildViews;$(MSDeployPublishDependsOn);&lt;span class="nt"&gt;&amp;lt;/MSDeployPublishDependsOn&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"MvcBuildViews"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(MvcBuildViews)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;AspNetCompiler&lt;/span&gt; &lt;span class="na"&gt;VirtualPath=&lt;/span&gt;&lt;span class="s"&gt;"temp"&lt;/span&gt; &lt;span class="na"&gt;PhysicalPath=&lt;/span&gt;&lt;span class="s"&gt;"$(WebProjectOutputDir)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will modify the build process to ensure that MvcBuildViews is performed before publishing (by default, it is performed after publish step) and will allow to drop an error from compiler about web.config. However, it will still not produce precompiled IL for views and the application must build it on runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second try: Built-in views precompilation
&lt;/h2&gt;

&lt;p&gt;It turns out that the Visual Studio team already thought about those, who wish to use precompiled views and embedded set of parameters to be set to achieve this goal, which is invoked on publish of web application. So, it pushes us to using out-of-webroot development, which is beneficial for all (allows to tackle edge cases, encountered during publishing to production; makes your solution smaller and isolates delta of your custom work on top of the vanilla install), because view precompilation alters *.cshtml files (they are left there as a placeholders, with placeholder text).&lt;/p&gt;

&lt;p&gt;So, I used the publishing wizard and configured it as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--haueyxuZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2017/03/Schermafbeelding-2017-03-24-om-11.38.29-600x340.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--haueyxuZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2017/03/Schermafbeelding-2017-03-24-om-11.38.29-600x340.png" alt="VS screeenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If one will set ‘Allow precompiled site to be updateable’ – precompilation will not fire up for Views (at least, it does not produce AppCode.dll in bin folder of published app). This ended up in the following publish profile (all parameters there can be copied directly in csproj file of your MVC web app):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;ToolsVersion=&lt;/span&gt;&lt;span class="s"&gt;"4.0"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;WebPublishMethod&amp;gt;&lt;/span&gt;FileSystem&lt;span class="nt"&gt;&amp;lt;/WebPublishMethod&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;LastUsedBuildConfiguration&amp;gt;&lt;/span&gt;Debug&lt;span class="nt"&gt;&amp;lt;/LastUsedBuildConfiguration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;LastUsedPlatform&amp;gt;&lt;/span&gt;Any CPU&lt;span class="nt"&gt;&amp;lt;/LastUsedPlatform&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;SiteUrlToLaunchAfterPublish&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;LaunchSiteAfterPublish&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/LaunchSiteAfterPublish&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ExcludeApp_Data&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/ExcludeApp_Data&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;publishUrl&amp;gt;&lt;/span&gt;../../Published&lt;span class="nt"&gt;&amp;lt;/publishUrl&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;DeleteExistingFiles&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/DeleteExistingFiles&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PrecompileBeforePublish&amp;gt;&lt;/span&gt;True&lt;span class="nt"&gt;&amp;lt;/PrecompileBeforePublish&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;EnableUpdateable&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/EnableUpdateable&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;DebugSymbols&amp;gt;&lt;/span&gt;True&lt;span class="nt"&gt;&amp;lt;/DebugSymbols&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;WDPMergeOption&amp;gt;&lt;/span&gt;MergeAllOutputsToASingleAssembly&lt;span class="nt"&gt;&amp;lt;/WDPMergeOption&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;UseMerge&amp;gt;&lt;/span&gt;True&lt;span class="nt"&gt;&amp;lt;/UseMerge&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;SingleAssemblyName&amp;gt;&lt;/span&gt;AppCode&lt;span class="nt"&gt;&amp;lt;/SingleAssemblyName&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;DeleteAppCodeCompiledFiles&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/DeleteAppCodeCompiledFiles&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then I tried to publish my web app and … it fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Catch 1: Umbraco and Umbraco_client folders
&lt;/h3&gt;

&lt;p&gt;Due to some historical reasons, we’ve included both umbraco and umbraco_client in the solution, which lead to a precompilation failure. There are several ascx files, which are referring to backend code which is not present anymore. However, if Umbraco is installed via a NuGet package – it is not needed to include umbraco and umbraco_client folders in the solution, because nuget will import a set of tasks, which is responsible for copying umbraco and umbraco_client content to web deploy package and solution root during build. So, I excluded those folders again, and … it failed again. This time it failed because we have had some plugins installed and included in our solution, which was using masters from umbraco folders. I Included those masters files – and this time, precompilation was OK.&lt;/p&gt;

&lt;h3&gt;
  
  
  Catch 2: App_Code folder
&lt;/h3&gt;

&lt;p&gt;I expected that after publishing my app will start up fast and will not spend any time on runtime precompilation, but – I received YSOD, stating that App_Code folder is not allowed in precompiled applications. I checked filesystem and found out that App_code with some cs files is present there. It turned out that we have had some cs files, marked as content in this folder and used by nuPicker. I changed their attribute to ‘Compile’ and published once again (clearing target folder manually, as my publish profile says to keep existing files by setting DeleteExistingFiles to False – this is needed as we have a separate frontend build, which uses Gulp for generating it). After that, application shown up and startup time improved greatly (though, initial index build requires some time, but subsequent publishes result in fast application start).&lt;/p&gt;

&lt;h3&gt;
  
  
  Catch 3: Long publish times
&lt;/h3&gt;

&lt;p&gt;Views precompilation is good, but there is one immediately observed tradeoff – time to build increases greatly (on HDD can be up to 5 minutes – the same as we spent on startup before precompilation). So, in order to benefit from fast startup, my colleague Jeroen, who initially came with the idea of this feature, added a simple postbuild event in our MVC application csproj file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PostBuildEvent&amp;gt;&lt;/span&gt;XCOPY "$(ProjectDir)bin\*.dll" "$(ProjectDir)..\..\Published\bin\" /S /Y /i&lt;span class="nt"&gt;&amp;lt;/PostBuildEvent&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see – after build it will copy all dll’s from project bin to published folder, where precompiled app resides. So, if one has not changed anything in views – he does not need to launch full publish, but just build the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Catch 4: Could not login to Umbraco
&lt;/h3&gt;

&lt;p&gt;After playing enough with frontend, we’ve found out that trying to login to Umbraco results in YSOD. Quick check revealed its source – since our application is precompiled and not updateable (see publish profile example) it is expecting that all parts of our application are precompiled in the same manner. But, as noted before, we could not precompile Umbraco and umbraco_client folders, as this leads to failures in precompilation stage. Looks like a real problem, but we’ve found 2 possible solutions:&lt;/p&gt;

&lt;p&gt;1) We can trick runtime: when app is precompiled, compiler will drop PrecompiledApp.config in webroot and add a definition there, if app is updateable or not: So, to allow using non-precompiled Umbraco views with precompiled web app views – just set updateable to true This can be done by adding the following to MVC app csproj file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"__localPublishAllowUpdateable"&lt;/span&gt; &lt;span class="na"&gt;AfterTargets=&lt;/span&gt;&lt;span class="s"&gt;"CopyAllFilesToSingleFolderForPackage"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(PrecompileBeforePublish) And !$(EnableUpdateable)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- This target is used with local publish --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt; &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectFile)"&lt;/span&gt; &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"__AllowUpdateable"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"__MsDeployPublishAllowUpdateable"&lt;/span&gt; &lt;span class="na"&gt;AfterTargets=&lt;/span&gt;&lt;span class="s"&gt;"CopyAllFilesToSingleFolderForMsdeploy"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(PrecompileBeforePublish) And !$(EnableUpdateable)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- This target is used with msdeploy publish --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt; &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectFile)"&lt;/span&gt; &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"__AllowUpdateable"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"__AllowUpdateable"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(PrecompileBeforePublish) And !$(EnableUpdateable)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- This target is required to mark precompiled app, based on Umbraco CMS as updateable --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;___IntermediateOutputPath&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$([System.IO.Path]::IsPathRooted($(IntermediateOutputPath)))' == 'False'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$(MSBuildProjectDirectory)\$(IntermediateOutputPath)&lt;span class="nt"&gt;&amp;lt;/___IntermediateOutputPath&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;___IntermediateOutputPath&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$([System.IO.Path]::IsPathRooted($(IntermediateOutputPath)))' == 'True'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$(IntermediateOutputPath)&lt;span class="nt"&gt;&amp;lt;/___IntermediateOutputPath&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Exec&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;'copy "$(ProjectDir)Properties\BuildTargets\PrecompiledApp.source" "$(___IntermediateOutputPath)Package\PackageTmp\PrecompiledApp.config" /y'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;RemoveDir&lt;/span&gt; &lt;span class="na"&gt;Directories=&lt;/span&gt;&lt;span class="s"&gt;"$(___IntermediateOutputPath)Package\PackageTmp\App_Code"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;These targets will copy PrecompiledApp.source file from Properties\BuildTargets of MVC web app project to output folder of published app. Content of PrecompiledApp.source is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;precompiledApp&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;updatable=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Actually, this can be achieved by importing this nuget - &lt;a href="http://www.nuget.org/packages/Colours.Ci.Umbraco/"&gt;http://www.nuget.org/packages/Colours.Ci.Umbraco/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2) Another possible solution, which also addresses catch number 5 and can be used to test load balanced solution is to add 2 IIS sites: one is pointing to published folder, and we will use it to check web app performance, check changes in code and views to be displayed correctly, second – pointing to folder with MVC project, which we will use to access Umbraco. In such approach, besides all other benefits, we can easily test load balanced approach (changing content in Umbraco shall be reflected in our published precompiled web app, events shall be called on both web apps and etc).&lt;/p&gt;

&lt;h3&gt;
  
  
  Catch 5: Could not debug views
&lt;/h3&gt;

&lt;p&gt;Despite the fact, that we are publishing in Debug mode and are adding DebugSymbols (see publishing profile) – views could not be debugged when debugger is attached to published web app process. Hence, Jeroen came up with a solution, described as point 2 in Catch 4. At this moment, I could not figure out how to debug views in precompiled application – maybe someone can come up with suggestions?&lt;/p&gt;

&lt;h3&gt;
  
  
  Catch 6: VPP is not working
&lt;/h3&gt;

&lt;p&gt;Our Umbraco based web app has the following setup: it is storing images at Azure blob storage via UmbracoFileSystemProviders.Azure, processes them via ImageProcessor and serves them via Azure CDN with the help of Our.Umbraco.AzureCDNToolkit. Final problem we faced was – our web app was not able to serve media directly from our virtual path provider (VPP), without processing it via ImageProcessor. So, all request like &lt;a href="http://mydevhost/media/1000/1.jpg"&gt;http://mydevhost/media/1000/1.jpg&lt;/a&gt; was serving ASP.NET 404, while requests like &lt;a href="http://mydevhost/media/1000/1.jpg?1=1"&gt;http://mydevhost/media/1000/1.jpg?1=1&lt;/a&gt; will be happily picked up by ImageProcessor and processed further. Not a big flaw, and, in some setups, it can be even treated like an additional source of protection for original images (imagine that you do not want to serve original media items for your end-users in any circumstances). Actually, this is by design: &lt;a href="http://msdn2.microsoft.com/en-us/library/system.web.hosting.virtualpathprovider.aspx"&gt;http://msdn2.microsoft.com/en-us/library/system.web.hosting.virtualpathprovider.aspx&lt;/a&gt; notes that VPP will not work in precompiled web application.&lt;br&gt;
However, in &lt;a href="https://our.umbraco.org/forum/using-umbraco-and-getting-started/84018-umbraco-views-precompilation-and-virtual-path-providers"&gt;this forum thread&lt;/a&gt; I came up with an answer how we can hack it and allow usage of particular VPP in precompiled application.&lt;/p&gt;
&lt;h2&gt;
  
  
  Teamcity configuration
&lt;/h2&gt;

&lt;p&gt;Since we are using direct publish from Teamcity (system.DeployOnBuild is set to True) via MsDeployPublish – we have 2 options to set this up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define all parameters in additional publish profile and pass its name to Teamcity build&lt;/li&gt;
&lt;li&gt;Add following parameters as system to the build configuration itself:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system.EnableUpdateable = False
system.PrecompileBeforePublish = True
system.SingleAssemblyName = nameOfTheAssembly
system.UseMerge = True
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;However, in my situation, despite of the fact that I have had Visual Studio 2015 and SDK’s for .NET versions installed – build was failing, because Teamcity was not able to find aspnet_merge.exe, which is part of the SDK. So, I defined 2 additional parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system.AspnetMergePath = “C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\”
system.GetAspNetMergePath = False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After this, my builds were green again and I got a precompiled application also on our production environment, which is boasting faster startup times after IIS recycle or VM restart for updates installation.&lt;/p&gt;

</description>
      <category>umbraco</category>
      <category>devlive</category>
    </item>
    <item>
      <title>Automated umbracoSettings.config modifications</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Mon, 19 Aug 2019 08:21:11 +0000</pubDate>
      <link>https://dev.to/akuryan/automated-umbracosettings-config-modifications-56h9</link>
      <guid>https://dev.to/akuryan/automated-umbracosettings-config-modifications-56h9</guid>
      <description>&lt;p&gt;This is cross-post from &lt;a href="https://dobryak.org/automated-umbracosettings-config-modifications/"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is one more piece of automation puzzle to save time and make the life of a build engineer easier. This part is targeted towards removing double work of redoing things that are already done on  Teamcity level configurations of Umbraco-driven Continuous Delivery (CD) projects.&lt;/p&gt;

&lt;p&gt;Umbraco is an open-source ASP.NET CMS with a big developers community. One of the features of this CMS, which makes automated deliveries somewhat harder to configure for build engineer, is a configuration file system, which consists out of web.config and bunch of other .config files, stored in the ~/config directory. This is brilliant idea - each Umbraco part is driven by its own configuration file, but it becomes a nightmare when you have to setup some environment-specific settings in some of these files. Configuration file transformation, even in Visual Studio 2015, allows you to build web.config file transforms only. Of course, there is the SlowCheetah plugin, which alleviates this problem, but it generates extra work for the build engineer. Since we have an established automated Continuous Delivery process, our main target is to remove all those small distractive pieces of configuration and automate everything J.&lt;/p&gt;

&lt;p&gt;In this post I will cover automated modifications on one of the two parts of the umbracoSettings.config file, responsible for KeepAliver, task scheduling and delayed publishing in Umbraco.&lt;/p&gt;

&lt;p&gt;To my great grieve, Umbraco has two of such parameters, which are version dependent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;for versions 6.2.5 and 7.1.9-7.2.7 it is the baseUrl attribute of the scheduledTasks element (since 7.2.7 it is obsolete, in previous versions it is not present)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;since 7.2.7 it is the umbracoApplicationUrl attribute of the web.routing element&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Target of both: remove automated guess from first request about Umbraco backoffice access URL and set it up by configuration (fully, they are covered at &lt;a href="https://our.umbraco.org/documentation/reference/config/umbracosettings/"&gt;https://our.umbraco.org/documentation/reference/config/umbracosettings/&lt;/a&gt; and &lt;a href="http://issues.umbraco.org/issue/U4-6788"&gt;http://issues.umbraco.org/issue/U4-6788&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Code and explanations
&lt;/h2&gt;

&lt;p&gt;First of all, we have to modify the MsBuild compilation process to make sure that this task is executed directly after compilation and before configuration files transformation, by adding it to the centralized project file PropertyGroup element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetsTriggeredByCompilation&amp;gt;&lt;/span&gt;
        $(TargetsTriggeredByCompilation);
        UmbracoSettingsConfigTransform; 
    &lt;span class="nt"&gt;&amp;lt;/TargetsTriggeredByCompilation&amp;gt;&lt;/span&gt;    
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will ensure that our target UmbracoSettingsConfigTransform is called directly after compilation.  Next to it, as flexibility is required, in the same PropertyGroup we add some variables, which allows to stop automated transformation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- If system.DoNotSetScheduledTasksBaseUrl is not defined - it shall be equal to false --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DoNotSetScheduledTasksBaseUrl&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(DoNotSetScheduledTasksBaseUrl)'==''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/DoNotSetScheduledTasksBaseUrl&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!--
As seen by name starting with underscores - __SetScheduledTasksBaseUrl - is internal variable.
It's target - to establish setup of scheduled task base url
--&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;__SetScheduledTasksBaseUrl&amp;gt;&lt;/span&gt;!$(DoNotSetScheduledTasksBaseUrl)&lt;span class="nt"&gt;&amp;lt;/__SetScheduledTasksBaseUrl&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- If system.DoNotSetUmbracoApplicationUrl is not defined - it shall be equal to false --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DoNotSetUmbracoApplicationUrl&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(DoNotSetUmbracoApplicationUrl)'==''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;False&lt;span class="nt"&gt;&amp;lt;/DoNotSetUmbracoApplicationUrl&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;__SetUmbracoApplicationUrl&amp;gt;&lt;/span&gt;!$(DoNotSetUmbracoApplicationUrl)&lt;span class="nt"&gt;&amp;lt;/__SetUmbracoApplicationUrl&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As one can see, if the variables DoNotSetScheduledTasksBaseUrl and DoNotSetUmbracoApplicationUrl are not set, they are made False, thus allowing further transforms. If variables are set, the build process will receive the corresponding values. After that, the build process will copy the original, unmodified version to temporary storage. After the build is finished, the unmodified version will be placed back to alleviate possible version control system (VCS) checkout issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"CopySourceTransformFiles"&lt;/span&gt; &lt;span class="na"&gt;BeforeTargets=&lt;/span&gt;&lt;span class="s"&gt;"Cleanup"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Copy&lt;/span&gt; &lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(UmbracoSettingsConfigFullPath)"&lt;/span&gt; &lt;span class="na"&gt;DestinationFolder=&lt;/span&gt;&lt;span class="s"&gt;"$(IntermediateOutputPath)"&lt;/span&gt; &lt;span class="na"&gt;OverwriteReadOnlyFiles=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"RestoreSourceTransformFiles"&lt;/span&gt; &lt;span class="na"&gt;BeforeTargets=&lt;/span&gt;&lt;span class="s"&gt;"Cleanup"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Copy&lt;/span&gt; &lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(IntermediateOutputPath)$(UmbracoSettingsConfigName)"&lt;/span&gt; &lt;span class="na"&gt;DestinationFolder=&lt;/span&gt;&lt;span class="s"&gt;"$(umbracoConfigFolder)"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetScheduledTasksBaseUrl) Or $(__SetUmbracoApplicationUrl)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next to it, we have to check, if one of 2 possible modifications is valid for the current Umbraco version. Umbraco stores the currently installed version in web.config, in the appSettings section, in the value of a key umbracoConfigurationStatus. So, on build, code is retrieving this value using XmlPeek MsBuild task, then, using Nuget package SemanticVersioning (&lt;a href="https://www.nuget.org/packages/SemanticVersioning/%D0%9E%D1%82%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D0%B5%D1%82%D1%81%D1%8F"&gt;https://www.nuget.org/packages/SemanticVersioning/Открывается&lt;/a&gt; в новом окне,&lt;a href="https://github.com/adamreeve/semver.net%D0%9E%D1%82%D0%BA%D1%80%D1%8B%D0%B2%D0%B0%D0%B5%D1%82%D1%81%D1%8F"&gt;https://github.com/adamreeve/semver.netОткрывается&lt;/a&gt; в новом окне) version is compared against the range using custom MsBuild tasks and the internal variable receives True or False value (to allow or disallow modification):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;UsingTask&lt;/span&gt; &lt;span class="na"&gt;TaskName=&lt;/span&gt;&lt;span class="s"&gt;"CompareSemanticVersions"&lt;/span&gt; &lt;span class="na"&gt;AssemblyFile=&lt;/span&gt;&lt;span class="s"&gt;"$(TeamCityCiToolsPath)\CI.Builds\MsBuildCustomTasks\Colours.Ci.MSBuild.CustomTasks.dll"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"EstablishConfigFileModificationsRequirements"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__umbraco_config_setUmbracoUrl)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Get version from web.config --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;XmlPeek&lt;/span&gt; &lt;span class="na"&gt;Namespaces=&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;lt;Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/&amp;amp;gt;"&lt;/span&gt;
          &lt;span class="na"&gt;XmlInputPath=&lt;/span&gt;&lt;span class="s"&gt;"web.config"&lt;/span&gt;
          &lt;span class="na"&gt;Query=&lt;/span&gt;&lt;span class="s"&gt;"configuration/appSettings/add[@key = 'umbracoConfigurationStatus']/@value"&lt;/span&gt;
          &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('web.config')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Output&lt;/span&gt; &lt;span class="na"&gt;TaskParameter=&lt;/span&gt;&lt;span class="s"&gt;"Result"&lt;/span&gt; &lt;span class="na"&gt;PropertyName=&lt;/span&gt;&lt;span class="s"&gt;"__umbracoVersionFromWebConfig"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/XmlPeek&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Umbraco version peeked from web.config: $(__umbracoVersionFromWebConfig)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!--
            Now we have to check, which Umbraco version we are working with.
            baseUrl for scheduled tasks are allowed in 6.2.5 and 7.1.9 - 7.2.7
            If version does not fit in this range - baseUrl shall not be set.
            If version is 7.2.7 and higher - web.routing attribute umbracoApplicationUrl shall be used
        --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;CompareSemanticVersions&lt;/span&gt; &lt;span class="na"&gt;CurrentVersion=&lt;/span&gt;&lt;span class="s"&gt;"$(__umbracoVersionFromWebConfig)"&lt;/span&gt; &lt;span class="na"&gt;AllowedVersionRange=&lt;/span&gt;&lt;span class="s"&gt;"6.2.5 || &amp;amp;gt;=7.1.9 &amp;amp;lt;7.2.7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Output&lt;/span&gt; &lt;span class="na"&gt;TaskParameter=&lt;/span&gt;&lt;span class="s"&gt;"VersionIsInRange"&lt;/span&gt; &lt;span class="na"&gt;PropertyName=&lt;/span&gt;&lt;span class="s"&gt;"__SetScheduledTasksBaseUrl"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/CompareSemanticVersions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"We are going to set scheduledTasks baseUrl"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetScheduledTasksBaseUrl)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"We are NOT going to set scheduledTasks baseUrl"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"!$(__SetScheduledTasksBaseUrl)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt; &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectFile)"&lt;/span&gt; &lt;span class="na"&gt;Properties=&lt;/span&gt;&lt;span class="s"&gt;"CustomConfigFileToTransfom=$(_PackageTempDir)\$(UmbracoSettingsConfigFullPath);CustomConfigTransformFile=$(TeamCityCiToolsPath)\CI.Builds\SharedFiles\Umbraco\umbraco.umbracosettings.scheduledurl.config.transform"&lt;/span&gt; &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"TransformCustomConfigFile"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetScheduledTasksBaseUrl) AND Exists('$(_PackageTempDir)\$(UmbracoSettingsConfigFullPath)')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;CompareSemanticVersions&lt;/span&gt; &lt;span class="na"&gt;CurrentVersion=&lt;/span&gt;&lt;span class="s"&gt;"$(__umbracoVersionFromWebConfig)"&lt;/span&gt; &lt;span class="na"&gt;AllowedVersionRange=&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;gt;=7.2.7"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Output&lt;/span&gt; &lt;span class="na"&gt;TaskParameter=&lt;/span&gt;&lt;span class="s"&gt;"VersionIsInRange"&lt;/span&gt; &lt;span class="na"&gt;PropertyName=&lt;/span&gt;&lt;span class="s"&gt;"__SetUmbracoApplicationUrl"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/CompareSemanticVersions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"We are going to set web.routing umbracoApplicationUrl"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetUmbracoApplicationUrl)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"We are NOT going to set web.routing umbracoApplicationUrl"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"!$(__SetUmbracoApplicationUrl)"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt; &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectFile)"&lt;/span&gt; &lt;span class="na"&gt;Properties=&lt;/span&gt;&lt;span class="s"&gt;"CustomConfigFileToTransfom=$(_PackageTempDir)\$(UmbracoSettingsConfigFullPath);CustomConfigTransformFile=$(TeamCityCiToolsPath)\CI.Builds\SharedFiles\Umbraco\umbraco.umbracosettings.umbracoApplicationUrl.config.transform"&lt;/span&gt; &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"TransformCustomConfigFile"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetUmbracoApplicationUrl) AND Exists('$(_PackageTempDir)\$(UmbracoSettingsConfigFullPath)')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I wish to focus on custom task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;CompareSemanticVersions&lt;/span&gt;
&lt;span class="na"&gt;CurrentVersion=&lt;/span&gt;&lt;span class="s"&gt;"$(__umbracoVersionFromWebConfig)"&lt;/span&gt;
&lt;span class="na"&gt;AllowedVersionRange=&lt;/span&gt;&lt;span class="s"&gt;"6.2.5 || &amp;amp;gt;=7.1.9 &amp;amp;lt;7.2.7"&lt;/span&gt;
&lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetScheduledTasksBaseUrl)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Output&lt;/span&gt; &lt;span class="na"&gt;TaskParameter=&lt;/span&gt;&lt;span class="s"&gt;"VersionIsInRange"&lt;/span&gt; &lt;span class="na"&gt;PropertyName=&lt;/span&gt;&lt;span class="s"&gt;"__SetScheduledTasksBaseUrl"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/CompareSemanticVersions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Code of this task is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Colours.Ci.MSBuild.CustomTasks.HelperMethods.StringMethods&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Loging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Build.Framework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Build.Utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SemVer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SemVer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CompareSemanticVersions&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// Current version of software&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CurrentVersion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// Allowed versions range of software.&lt;/span&gt;
        &lt;span class="c1"&gt;/// Shall use ranges, as described https://github.com/npm/node-semver#ranges&lt;/span&gt;
        &lt;span class="c1"&gt;/// Also, see https://github.com/adamreeve/semver.net#ranges&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AllowedVersionRange&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// Demonstrates, if version is in range&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;VersionIsInRange&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;VersionIsInRange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CheckVersions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CurrentVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AllowedVersionRange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;CheckVersions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;allowedVersionRange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;logWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Logging&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;logWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"There was an error "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"while parsing "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowedVersionRange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsSatisfied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Actually, it is just a code, compiled to assembly. It receives the current version in CurrentVersion parameter, a range of allowed versions in AllowedVersionsRange parameters in NPM notation (&lt;a href="https://github.com/npm/node-semver#ranges"&gt;https://github.com/npm/node-semver#ranges&lt;/a&gt;) and outputs Boolean variable. The same task is called to check version requirements for the umbracoApplicationUrl parameter. There is one MsBuild feature which shall be known - characters ‘&amp;lt;’ and ‘&amp;gt;’ must be escaped. The symbol ‘&amp;lt;’ is represented as ‘&amp;lt;’. The symbol ‘&amp;gt;’ is represented as ‘&amp;gt;’. After checking versions, we have to modify umbracoSettings.config by using the XmlTransform task, as (surprise!!!) XmlPoke tasks, used to add URL to attribute could not insert missing attributes. So, it was added static transform files, which just adds attributes with an ‘empty’ value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt;
&lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectFile)"&lt;/span&gt;
&lt;span class="na"&gt;Properties=&lt;/span&gt;&lt;span class="s"&gt;"CustomConfigFileToTransfom=$(UmbracoSettingsConfigFullPath);CustomConfigTransformFile=$(TeamCityCiToolsPath)\CI.Builds\SharedFiles\Umbraco\umbraco.umbracosettings.umbracoApplicationUrl.config.transform"&lt;/span&gt;
&lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"TransformCustomConfigFile"&lt;/span&gt;
&lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetUmbracoApplicationUrl)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"TransformCustomConfigFile"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('$(CustomConfigFileToTransfom)') And Exists('$(CustomConfigTransformFile)')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;RandomNameTask&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Output&lt;/span&gt; &lt;span class="na"&gt;TaskParameter=&lt;/span&gt;&lt;span class="s"&gt;"RandomName"&lt;/span&gt; &lt;span class="na"&gt;PropertyName=&lt;/span&gt;&lt;span class="s"&gt;"RandomName"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/RandomNameTask&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Somebody have to ensure, that he have original file copied - but this shall not be done here, IMHO --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- let us do a transformation --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MakeDir&lt;/span&gt; &lt;span class="na"&gt;Directories=&lt;/span&gt;&lt;span class="s"&gt;"$(IntermediateOutputPath)$(RandomName)\"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;TransformXml&lt;/span&gt;
&lt;span class="na"&gt;Source=&lt;/span&gt;&lt;span class="s"&gt;"$(CustomConfigFileToTransfom)"&lt;/span&gt;
&lt;span class="na"&gt;Destination=&lt;/span&gt;&lt;span class="s"&gt;"$(IntermediateOutputPath)$(RandomName)\transformed.$(RandomName).config"&lt;/span&gt;
&lt;span class="na"&gt;Transform=&lt;/span&gt;&lt;span class="s"&gt;"$(CustomConfigTransformFile)"&lt;/span&gt;
&lt;span class="na"&gt;StackTrace=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- now, result of transformation shall be copied to original place --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Copy&lt;/span&gt;
&lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(IntermediateOutputPath)$(RandomName)\transformed.$(RandomName).config"&lt;/span&gt;
&lt;span class="na"&gt;DestinationFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(CustomConfigFileToTransfom)"&lt;/span&gt;
&lt;span class="na"&gt;OverwriteReadOnlyFiles=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- And intermediate results shall be deleted --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Delete&lt;/span&gt; &lt;span class="na"&gt;Files=&lt;/span&gt;&lt;span class="s"&gt;"$(IntermediateOutputPath)$(RandomName)\transformed.$(RandomName).config"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;CustomTransformFile target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="c"&gt;&amp;lt;!--
        Next target can be used to transform any input xml file by using transform file.
        You can invoke this target by following:
        &amp;lt;MSBuild
        Projects="$(MSBuildProjectFile)"
        Properties="CustomConfigFileToTransfom=$(TeamCityCiToolsPath)\CI.Builds\SharedFiles\Umbraco\umbraco.web.config;CustomConfigTransformFile=$(TeamCityCiToolsPath)\CI.Builds\SharedFiles\Umbraco\umbraco.web.config.transform"
        Targets="TransformCustomConfigFile" /&amp;gt;

        Idea is that you a passing source config file and transform for it here
    --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"TransformCustomConfigFile"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('$(CustomConfigFileToTransfom)') And Exists('$(CustomConfigTransformFile)')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"************************************************************************"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"TransformCustomConfigFile is runing :)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Transform file is $(CustomConfigTransformFile)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Transformimg following file - $(CustomConfigFileToTransfom)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"************************************************************************"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RandomNameTask&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Output&lt;/span&gt; &lt;span class="na"&gt;TaskParameter=&lt;/span&gt;&lt;span class="s"&gt;"RandomName"&lt;/span&gt; &lt;span class="na"&gt;PropertyName=&lt;/span&gt;&lt;span class="s"&gt;"RandomName"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/RandomNameTask&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Somebody have to ensure, that he have original file copied - but this shall not be done here, IMHO --&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- let us do a transformation --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;MakeDir&lt;/span&gt; &lt;span class="na"&gt;Directories=&lt;/span&gt;&lt;span class="s"&gt;"$(IntermediateOutputPath)$(RandomName)\"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;__fileToTransform&amp;gt;&lt;/span&gt;$(IntermediateOutputPath)$(RandomName)\source.$(RandomName).config&lt;span class="nt"&gt;&amp;lt;/__fileToTransform&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;__transformedFile&amp;gt;&lt;/span&gt;$(IntermediateOutputPath)$(RandomName)\transformed.$(RandomName).config&lt;span class="nt"&gt;&amp;lt;/__transformedFile&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Copy original file for transformation to avoid locking issues --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Copy&lt;/span&gt;
            &lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(CustomConfigFileToTransfom)"&lt;/span&gt;
            &lt;span class="na"&gt;DestinationFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(__fileToTransform)"&lt;/span&gt;
            &lt;span class="na"&gt;OverwriteReadOnlyFiles=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TransformXml&lt;/span&gt;
            &lt;span class="na"&gt;Source=&lt;/span&gt;&lt;span class="s"&gt;"$(__fileToTransform)"&lt;/span&gt;
            &lt;span class="na"&gt;Destination=&lt;/span&gt;&lt;span class="s"&gt;"$(__transformedFile)"&lt;/span&gt;
            &lt;span class="na"&gt;Transform=&lt;/span&gt;&lt;span class="s"&gt;"$(CustomConfigTransformFile)"&lt;/span&gt;
            &lt;span class="na"&gt;StackTrace=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- now, result of transformation shall be copied to original place --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Delete&lt;/span&gt; &lt;span class="na"&gt;Files=&lt;/span&gt;&lt;span class="s"&gt;"$(CustomConfigFileToTransfom)"&lt;/span&gt; &lt;span class="na"&gt;ContinueOnError=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Copy&lt;/span&gt;
            &lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(__transformedFile)"&lt;/span&gt;
            &lt;span class="na"&gt;DestinationFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(CustomConfigFileToTransfom)"&lt;/span&gt;
            &lt;span class="na"&gt;OverwriteReadOnlyFiles=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- And intermediate results shall be deleted --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Delete&lt;/span&gt; &lt;span class="na"&gt;Files=&lt;/span&gt;&lt;span class="s"&gt;"$(__transformedFile)"&lt;/span&gt; &lt;span class="na"&gt;ContinueOnError=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Delete&lt;/span&gt; &lt;span class="na"&gt;Files=&lt;/span&gt;&lt;span class="s"&gt;"$(__fileToTransform)"&lt;/span&gt; &lt;span class="na"&gt;ContinueOnError=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"Config after TransformCustomConfigFile"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;MSBuild&lt;/span&gt; &lt;span class="na"&gt;Projects=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildProjectFile)"&lt;/span&gt; &lt;span class="na"&gt;Properties=&lt;/span&gt;&lt;span class="s"&gt;"_FileToRead=$(CustomConfigFileToTransfom)"&lt;/span&gt; &lt;span class="na"&gt;Targets=&lt;/span&gt;&lt;span class="s"&gt;"_ListFileContent"&lt;/span&gt; &lt;span class="na"&gt;ContinueOnError=&lt;/span&gt;&lt;span class="s"&gt;"True"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"End of config after TransformCustomConfigFile"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Final task is to use the system.website.hostname Teamcity variable and value of the umbracoPath key from web.config, to build the URL to access Umbraco with the help of an additional MsBuild task. I have to build such a cumbersome solution, because there are differences between how attribute values shall look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;baseUrl shall not contain schema, but port (e.g. it shall be something like mysite.com:80/Umbraco)&lt;/li&gt;
&lt;li&gt;umbracoApplicationUrl shall be just fully qualified domain name (FQDN) - (&lt;a href="http://mysite.com/umbraco"&gt;http://mysite.com/umbraco&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is done by using 2 built-in MsBuild XmlPoke tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;XmlPoke&lt;/span&gt; &lt;span class="na"&gt;Namespaces=&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;lt;Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/&amp;amp;gt;"&lt;/span&gt;
&lt;span class="na"&gt;XmlInputPath=&lt;/span&gt;&lt;span class="s"&gt;"$(UmbracoSettingsConfigFullPath)"&lt;/span&gt;
&lt;span class="na"&gt;Query=&lt;/span&gt;&lt;span class="s"&gt;"//settings/scheduledTasks/@baseUrl"&lt;/span&gt;
&lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"$(BaseUrl)"&lt;/span&gt;
&lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetScheduledTasksBaseUrl)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;XmlPoke&lt;/span&gt; &lt;span class="na"&gt;Namespaces=&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;lt;Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/&amp;amp;gt;"&lt;/span&gt;
&lt;span class="na"&gt;XmlInputPath=&lt;/span&gt;&lt;span class="s"&gt;"$(UmbracoSettingsConfigFullPath)"&lt;/span&gt;
&lt;span class="na"&gt;Query=&lt;/span&gt;&lt;span class="s"&gt;"//settings/web.routing/@umbracoApplicationUrl"&lt;/span&gt;
&lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"$(UmbracoApplicationUrl)"&lt;/span&gt;
&lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"$(__SetUmbracoApplicationUrl)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
      <category>umbraco</category>
      <category>ci</category>
    </item>
    <item>
      <title>Self-hosted agents at Azure DevOps: a little cost-saving trick</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Wed, 26 Dec 2018 13:16:01 +0000</pubDate>
      <link>https://dev.to/akuryan/self-hosted-agents-at-azure-devops-a-little-cost-saving-trick-4297</link>
      <guid>https://dev.to/akuryan/self-hosted-agents-at-azure-devops-a-little-cost-saving-trick-4297</guid>
      <description>&lt;p&gt;Azure DevOps does a great job when providing hosted agent services. They come loaded with all required software, they care about updates and everything else, but they have some major drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No static external IP-address (so it's not possible to have an additional layer of security))&lt;/li&gt;
&lt;li&gt;You get a new VM each time, so you need to clone your repository fresh, install a fresh set of NPM packages, install all those base Docker images (not a big deal for alpine-based images, but when it comes to Microsoft one, it is really a hitter)&lt;/li&gt;
&lt;li&gt;For closed source projects there is a hard limit of 1800 minutes per month per 1 hosted job (I do not like limits even if I never hit them 😀 )
And so on – you name it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To overcome this, one can deploy self-hosted agents, but then you'd have to deal with updates, installation of tooling and &lt;strong&gt;extra cost&lt;/strong&gt;. How to deal with updates and actual management is covered in &lt;a href="https://wouterdekort.com/2018/02/25/build-your-own-hosted-vsts-agent-cloud-part-1-build/"&gt;this blog post&lt;/a&gt;, but it still leaves the question of cost partially open. I have spent some time and did several improvements to the work of Wouter de Kort in a fork of his repository, check out at &lt;a href="https://github.com/akuryan/self-hosted-azure-devops-agents"&gt;my repository&lt;/a&gt;. Here I did some scripting optimization and improvement, but the main thing, which I wish to cover in this blog post, is the cost-optimization tool I built for our fleet of self-managed agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;We have had only 2 hosted VS2015/17 jobs for performing builds and releases for 10 projects, each of which was requiring anywhere from 5 to 20 minutes to build and somewhere between 15 and 30 minutes to release. That was quite taxing, especially when queues built-up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idea
&lt;/h2&gt;

&lt;p&gt;In our particular situation we have 7 parallel jobs for self-hosted agents, which comes for "free", through subscription to Visual Studio, so I began searching how to leverage those to improve our build and release speed. Initial setup was strictly following the &lt;a href="https://wouterdekort.com/2018/02/25/build-your-own-hosted-vsts-agent-cloud-part-1-build/"&gt;Wouter de Kort blog series&lt;/a&gt;, with automated switching of VMs in a Virtual Machines Scale set nightly and on weekends to save costs. But, as soon as I began receiving requests to start some VMs for the weekend or start them later at evening to fulfill some off-hour tasks, or earlier in the morning to do urgent deployments, I started seeking for a way to automate these tasks. Which lead to having the idea of a continuous web job, which will continuously monitor the queue in the target pool and start VMs when they are needed (and stop them when they are not needed).&lt;/p&gt;

&lt;h2&gt;
  
  
  Realization
&lt;/h2&gt;

&lt;p&gt;I came to Azure DevOps with a strong TeamCity background, so I was hoping to find something similar to a build queue in TeamCity, but, alas, they have another approach: all tasks are assigned to a pool, where they are picked up by the first agent to be online and free in a FIFO manner (first in, first out). There is no queue at all, all tasks just have a property "Result". If it is null, then this task has not yet been executed. If all tasks assigned to the pool have a non-null property "Result", then there is nothing to do. So, if there are some tasks in a pool with a null property "Result", then the code will check how much online agents are present in the pool. If the agent count is more than or equal to the tasks count, again, there is nothing to do. If the agent count is less than the tasks count, we need to start more virtual machines in the virtual machines scale set for our agents. If there are more agents online present in our pool than the number of assigned tasks, we need to stop extra agents in the virtual machines in scale set. Also, there is an option to define business hours and days of the week when there is a minimum required amount of agents online to speed up development (so, teams do not have to wait for an agent to become active and consume a task, but the task will be started immediately). The check to provision more VMs is done once every 2 minutes, to minimize waiting time and not abuse the API too much. The check to deprovision VMs is done once in 15 minutes, this allows more runtime for agents. Normally, I observe that almost immediately after a successful build a developer will wish to deploy to the Test environment.&lt;/p&gt;

&lt;p&gt;In my humble opinion, this solution is better than statically switching off/on virtual machines on some schedule, because it allows to fulfill any task (compile, test, release, whatever is executed on your agents) at any time in a Just-In-Time manner. Though, naturally, if all agents were switched off, it will take some time for them to become online, but due to the business hour / day option, this will only happen in off-hours.&lt;/p&gt;

&lt;p&gt;All settings and deployment instructions for the Autoscaler application are described &lt;a href="https://github.com/akuryan/self-hosted-azure-devops-agents/blob/master/autoscalingApp/README.md"&gt;here&lt;/a&gt;. I would not duplicate them in this blog post, as overtime they could change, and the readme document will be kept up-to-date.&lt;/p&gt;

&lt;p&gt;The code of the Autoscaler app can be seen at &lt;a href="https://github.com/akuryan/self-hosted-azure-devops-agents/tree/master/autoscalingApp/AgentsMonitor"&gt;this location&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is also an &lt;a href="https://github.com/akuryan/self-hosted-azure-devops-agents/tree/master/autoscalingApp/arm-template"&gt;ARM template&lt;/a&gt; for the baseline configuration of a web app which is suitable if you have only one pool in Azure DevOps to monitor, as it defines settings at an App Settings level of the web app itself. If there is more than one pool, only shared settings shall be defined in the App Settings of web app, while specific settings should be added to the App Settings of an individual web job. You can host as much web jobs as you need, but mind the web app limits.&lt;/p&gt;

&lt;p&gt;Be aware, that the ARM template by default will deploy to a D1 web app (Shared web app, which allows limited amount of CPU time and only 1 Gb of RAM &lt;strong&gt;without Always On&lt;/strong&gt;). The "Always On" feature ensures that the hosting process is always awake and is not shut down after 20 minutes of inactivity. So, if a web job will be deployed without additional precautions, it would not work, as the web app runtime will shut down the Kudu process after 20 minutes of inactivity. There is a nice trick to keep it up and running: you need to ping the Kudu homepage of your web app at least once every 20 minutes. I am using &lt;a href="https://www.happyapps.io/"&gt;https://www.happyapps.io/&lt;/a&gt; to visit the Kudu homepage of my web app once per 5 minutes on the address &lt;a href="https://webappName.scm.azurewebsites.net/"&gt;https://webappName.scm.azurewebsites.net/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment hints
&lt;/h2&gt;

&lt;p&gt;By default, the Azure Web app runtime does not execute the continuous web job from the path where it is deployed to, but I still wish to be sure that it is not running when I am deploying it, so I am using the following PowerShell scripts to stop/start the web job&lt;/p&gt;

&lt;p&gt;To stop webjob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;Invoke-AzureRmResourceAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ResourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ResourceType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Web/sites/continuouswebjobs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ResourceName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webAppName/webJobName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Stop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ApiVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2018-02-01&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To start webjob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nf"&gt;Invoke-AzureRmResourceAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ResourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ResourceType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Web/sites/continuouswebjobs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ResourceName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webAppName/webJobName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ApiVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2018-02-01&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The same scripts are used when rebuilding the Virtual Machines Scale set to ensure that the web job will not attempt to stop the VMs before they have been registered at the pool.&lt;/p&gt;

&lt;p&gt;This blog post have been created by me and edited by my friend &lt;a href="https://www.robhabraken.nl/"&gt;Rob Habraken&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>azuredevops</category>
    </item>
    <item>
      <title>Sitecore JSS: a blue green Node.js server configuration</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Fri, 23 Nov 2018 14:54:42 +0000</pubDate>
      <link>https://dev.to/akuryan/sitecore-jss-a-blue-green-node-js-server-configuration-4c2o</link>
      <guid>https://dev.to/akuryan/sitecore-jss-a-blue-green-node-js-server-configuration-4c2o</guid>
      <description>&lt;p&gt;The target of this post is to share how to configure Sitecore JSS headless with a Node.js proxy on CentOS and to demonstrate a staging approach for the hosting of the CD role. Since the front-end is rendered on a stand-alone Node proxy, we can easily configure as much backend CD servers as needed, which allows us to deploy CD code on a staging server, and then switch the target server on the Node instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js JSS server configuration on CentOS – instructions and scripts
&lt;/h2&gt;

&lt;p&gt;This part will show you an example of how you can configure your Node.js SSR server from scratch. For this I assume that you have basic knowledge of working in Linux.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd
&lt;/span&gt;wget linkGoesHere
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Go to your local users home and download the required Node.js release (replace &lt;code&gt;linkGoesHere&lt;/code&gt; in the following script with the link from &lt;a href="https://nodejs.org/en/download/"&gt;Node.Js download page&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;node
&lt;span class="nb"&gt;tar &lt;/span&gt;xvf node-v&lt;span class="k"&gt;*&lt;/span&gt;.tar.xz &lt;span class="nt"&gt;--strip-components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-C&lt;/span&gt; ./node
&lt;span class="nb"&gt;cd
rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node-v&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Create a directory, unpack the downloaded archive and remove it afterwards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;node
&lt;span class="nb"&gt;tar &lt;/span&gt;xvf node-v&lt;span class="k"&gt;*&lt;/span&gt;.tar.xz &lt;span class="nt"&gt;--strip-components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-C&lt;/span&gt; ./node
&lt;span class="nb"&gt;cd
rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; node-v&lt;span class="k"&gt;*&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Instruct npm where the symlinks shall be put:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd
mkdir &lt;/span&gt;node/etc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'prefix=/usr/local'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node/etc/npmrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Move Node.js to /opt and create the required symlinks to execute it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv &lt;/span&gt;node /opt/
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; root: /opt/node
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /opt/node/bin/node /usr/local/bin/node
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /opt/node/bin/npm /usr/local/bin/npm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When a command is executed with sudo, /usr/local/bin is excluded from the path. So, we need to add this path to the allowed paths (to have the possibility to call node and npm with sudo):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;visudo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Find the line that specifies Defaults secure_path and add &lt;code&gt;:/usr/local/bin&lt;/code&gt; to the end of it. &lt;br&gt;
should look like &lt;br&gt;
&lt;code&gt;Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We've just finished the installation of node.js . To verify this, execute &lt;code&gt;node -v&lt;/code&gt;, which will show you the installed Node.js version.&lt;/p&gt;

&lt;p&gt;To manage the SSR proxy and keep it up and running, we need a Node.js process manager. *nix world has a lot of them, so it can be difficult to pick one. I chose to use pm2, because it is simple, easy to use and has an option to either watch or ignore file system changes.&lt;/p&gt;

&lt;p&gt;So let's install and daemonize pm2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;pm2@latest &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;span class="c"&gt;#I want daemonize node app via current user, not root&lt;/span&gt;
&lt;span class="c"&gt;#follow instructions of command&lt;/span&gt;
pm2 startup systemd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After that, pm2 will start its service under the current user.&lt;/p&gt;

&lt;p&gt;Since Node.js apps is not big in security and caching (without special precautions), and setting up SSL on them is also quite a challenge, I will put Nginx in front of it to listen for requests on ports 80 and 443, and send them to my SSR proxy.&lt;/p&gt;

&lt;p&gt;To install Nginx, execute the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;epel-release
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;nginx
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/sbin/setsebool httpd_can_network_connect 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And ensure that Nginx does not expose its version to improve system security (we should disclose a minimum amount of data about our systems):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vi /etc/nginx/nginx.conf
&lt;span class="c"&gt;#Then, inside the http configuration part add the line server_tokens off; like this:&lt;/span&gt;
http &lt;span class="o"&gt;{&lt;/span&gt;
        server_tokens off&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then, configure SSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /etc/nginx/ssl/
&lt;span class="nb"&gt;sudo &lt;/span&gt;openssl dhparam &lt;span class="nt"&gt;-out&lt;/span&gt; /etc/nginx/ssl/dhparam.pem 4096
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Put your certificate and key into "/etc/nginx/ssl" and note the paths.&lt;br&gt;
Subsequently, restore the security configuration for the SSL certificates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;restorecon &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; /etc/nginx/ssl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And configure your application. Nginx will redirect all incoming requests on port 80 to port 443; on port 443, after a successful TLS handshake, all requests will be deciphered and sent to the Node.js app, hosted in pm2 and listening on port 3000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo touch&lt;/span&gt; /etc/nginx/conf.d/app.conf
&lt;span class="nb"&gt;sudo &lt;/span&gt;vi /etc/nginx/conf.d/app.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name _;
   return 301 https://$host$request_uri;
}
server {
  listen 443 ssl http2 default_server;
  listen [::]:443 ssl http2 default_server;
  server_name _;
  root /usr/share/nginx/html;
  ssl_protocols TLSv1.2;
  ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
  ssl_prefer_server_ciphers on;
  ssl_dhparam /etc/nginx/ssl/dhparam.pem;
  ssl_certificate /etc/nginx/ssl/YOURCERTIFICATE;
  ssl_certificate_key /etc/nginx/ssl/YOURKEY;
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-Port 443; 
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now check the configuration, restart Nginx and enable it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;span class="c"&gt;#if there is errors here – fix them&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Clone the Sitecore SSR proxy example and prepare for hosting it in PM2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;
&lt;span class="c"&gt;#SSR proxy configuration&lt;/span&gt;
git clone https://github.com/Sitecore/jss.git
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /opt/node-app/
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;YOURUSER: /opt/node-app/
&lt;span class="nb"&gt;cp &lt;/span&gt;jss/samples/node-headless-ssr-proxy/&lt;span class="k"&gt;**&lt;/span&gt; /opt/node-app/
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/node-app/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At this point, the application is still not functioning, but all further configuration will be done on VSTS.&lt;/p&gt;

&lt;h2&gt;
  
  
  VSTS release configuration to deploy to staging server and then switch it
&lt;/h2&gt;

&lt;p&gt;My release pipeline consists of several stages, but this blog post will not cover it all, as it would take a lot of time. Instead, I will concentrate on explaining how to do "green/blue" deployments for JSS.&lt;/p&gt;

&lt;p&gt;The main tools used during a release are an SSH connection to deliver changes to the CentOS host and MsDeploy to deliver changes to IIS.&lt;/p&gt;

&lt;p&gt;Our configuration is pretty simple: we are running on Sitecore 9.0.2 with JSS; our infrastructure consists of 1 CentOS webserver for the Node.js SSR proxy, 3 Windows servers (1 for CM, 1 for xConnect and 1 for the CD role). On the CD server I've configured 2 IIS websites, which names are the same as internal hostnames (cd1.host.local and cd2.host.local).&lt;/p&gt;

&lt;h3&gt;
  
  
  CD and Node.js delivery stage
&lt;/h3&gt;

&lt;p&gt;The first step of this stage is to check the Node.js proxy configuration, to understand on which server we should deliver our code now (so we are deploying to a website which is not under load ATM). It is done by connecting to our Node.js webserver and grabbing the hostname from the config (and since the hostname is the same as the IIS site name, that's all I need from there). Then this value is stored in a variable to be reused by subsequent steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;currentHost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A0&lt;/span&gt; &lt;span class="s1"&gt;'apiHost'&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;nodeApp.Path&lt;span class="si"&gt;)&lt;/span&gt;/config.js | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s2"&gt;"http(s)?:&lt;/span&gt;&lt;span class="se"&gt;\/\/&lt;/span&gt;&lt;span class="s2"&gt;(.*)'"&lt;/span&gt;|sed &lt;span class="s1"&gt;'s/https\?:\/\///'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;\)&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\{&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="se"&gt;\}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\1&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"##vso[task.setvariable variable=deploy.currSite;]&lt;/span&gt;&lt;span class="nv"&gt;$currentHost&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;deploymentHost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$currentHost&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"cd1.host.local"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cd2.host.local"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cd1.host.local"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"##vso[task.setvariable variable=deploy.iisSiteName;]&lt;/span&gt;&lt;span class="nv"&gt;$deploymentHost&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The variable "nodeApp.Path" contains the value "/opt/node-app", which is where we've deployed the node-headless-ssr-proxy sample from &lt;a href="https://github.com/Sitecore/jss.git"&gt;https://github.com/Sitecore/jss.git&lt;/a&gt; to, and the "deploy.iisSiteName" variable will now be holdingthe name of the target website (the one which is not under load). And then, even at first run, this script will not fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"$(msdeploy.Path)" -allowUntrusted="True" -verb:sync -source:recycleApp -dest:recycleApp="$(deploy.iisSiteName)",computerName="https://$(deploy.targetComputer):8172/msdeploy.axd?site=$(deploy.iisSiteName)",recycleMode="StopAppPool",username="$(user)",password="$(password)",authType="Basic"
"$(msdeploy.Path)" -allowUntrusted="True" -enableRule:DoNotDeleteRule -verb:sync -source:package="%sourcePath%" -dest:contentPath="$(deploy.iisSiteName)",computerName="https://$(deploy.targetComputer):8172/msdeploy.axd?site=$(deploy.iisSiteName)",username="$(user)",password="$(password)",authType="Basic"
"$(msdeploy.Path)" -allowUntrusted="True" -verb:sync -source:recycleApp -dest:recycleApp="$(deploy.iisSiteName)",computerName="https://$(deploy.targetComputer):8172/msdeploy.axd?site=$(deploy.iisSiteName)",recycleMode="StartAppPool",username="$(user)",password="$(password)",authType="Basic"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The next steps will stop the app pool, deploy a prepared application package and start it (to allow the CD server to preheat itself with an application init) via MsDeploy with the following commands:&lt;/p&gt;

&lt;p&gt;The next step is to update the Node.js app on the CentOS host. /dist/app content is prepared during web app compilation, so here we only need to update config.js with the new hostname. I am using &lt;a href="https://github.com/qetza/vsts-replacetokens-task#readme"&gt;https://github.com/qetza/vsts-replacetokens-task#readme&lt;/a&gt; to replace the token "deploy.iisSiteName" in config.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;nodeApp.dist.path&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-Rf&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;nodeApp.dist.path&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As soon as the files are ready to upload, I execute the following SSH script to cleanup the target path:&lt;/p&gt;

&lt;p&gt;"nodeApp.dist.path" can contain a unique value, generated from the static app name and the release number for example.&lt;/p&gt;

&lt;p&gt;Then I will deploy the files to the CentOS host via the Copy files Over SSH task and restart the pm2 application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;nodeApp.Path&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;#see https://github.com/Unitech/pm2/issues/325#issuecomment-281580956 for explanation&lt;/span&gt;
pm2 delete &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;nodeApp.Name&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; :
pm2 start index.js &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;nodeApp.Name&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At this point, our Node.js SSR proxy is pointing towards the newly updated IIS website. It then could be wise to stop the app pool of the 'old' CD IIS website with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"$(msdeploy.Path)" -allowUntrusted="True" -verb:sync -source:recycleApp -dest:recycleApp="$(deploy.currSite)",computerName="https://$(deploy.targetComputer):8172/msdeploy.axd?site=$(deploy.currSite)",recycleMode="StopAppPool",username="$(user)",password="$(password)",authType="Basic"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And that's it. You have now created a simple and effective blue green setup for your headless Sitecore JSS website using a Node.js proxy and multiple CD role servers to do the swapping.&lt;/p&gt;

&lt;p&gt;This blog post have been created by me and edited by my friend &lt;a href="https://www.robhabraken.nl/"&gt;Rob Habraken&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sitecore</category>
    </item>
    <item>
      <title>Sitecore Deployer</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Tue, 15 May 2018 08:14:37 +0000</pubDate>
      <link>https://dev.to/akuryan/sitecore-deployer-38el</link>
      <guid>https://dev.to/akuryan/sitecore-deployer-38el</guid>
      <description>&lt;p&gt;While working with Sitecore deployments based on ARM and PowerShell scripts, developed by Rob Habraken (Rob has covered them in a series of blog posts on his &lt;a href="https://www.robhabraken.nl/" rel="noopener noreferrer"&gt;own blog&lt;/a&gt;) and &lt;a href="https://www.robhabraken.nl/index.php/2959/sitecore-azure-scripts-update/" rel="noopener noreferrer"&gt;optimized by me&lt;/a&gt;, I noticed that we are having a problem: these scripts are used on a per-project basis and once implemented, they start aging. Since manually updating scripts for each project is a dull and cumbersome task, it usually is never executed. So I decided to build a &lt;a href="https://marketplace.visualstudio.com/items?itemName=anton-kuryan.SitecoreAzureVstsDeployer" rel="noopener noreferrer"&gt;VSTS release task&lt;/a&gt;, which could be reused in each of our projects and makes it easier to always have the latest version of the ARM and PowerShell scripts for a certain Sitecore version.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;You can check the PowerShell script at &lt;a href="https://github.com/akuryan/vsts.extensions/tree/master/SitecoreDeployer" rel="noopener noreferrer"&gt;my repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdobryak.org%2Fwp-content%2Fuploads%2F2018%2F11%2Fcolours-deployer-600x335.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdobryak.org%2Fwp-content%2Fuploads%2F2018%2F11%2Fcolours-deployer-600x335.png" alt="Sitecore Deployer setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This extension uses a VSTS connection endpoint to authorize against Azure and will take the following input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ARM template path&lt;/strong&gt;&lt;br&gt;
The ARM template published by the artifact source. Usually, it is a build.&lt;br&gt;
ARM parameters path&lt;br&gt;
The ARM parameters file for the template, published by the artifact source. They will be parsed at runtime and, in case of the ‘Deploy’ deployment type, combined with the output of the ‘Infrastructure’ deployment type and fed into template.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resource group name&lt;/strong&gt;&lt;br&gt;
The name of the targeted resource group, where the resources will reside. If the deployment type is ‘Infrastructure’ and the resource group doesn’t exist, the script will attempt to create it, but for the deployment type ‘Deploy’ it will throw an error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Location&lt;/strong&gt;&lt;br&gt;
A dropdown with the available locations for your resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deployment type&lt;/strong&gt;&lt;br&gt;
There are several differences between deployment types: the ‘Infrastructure’ deployment type will attempt to create the resource group if it doesn’t exist and provision the infrastructure, while the ‘Deploy’ deployment type will get the output of the ‘Infrastructure’ deployment type and combine it with the parameters in the template parameters file to deploy the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate SAS?&lt;/strong&gt;&lt;br&gt;
If this is set to Yes, then the script will try to generate a short-lived SAS for the SCWDP packages, passed in the parameters. The storage account must belong to the same subscription where we are deploying to. If it is not possible to generate a SAS, it will silently continue. If the license.xml file has to be retrieved from a URL and if it is stored on a storage account which belongs to the current subscription, the script will try to generate a SAS for it as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;License location&lt;/strong&gt;&lt;br&gt;
A dropdown which allows you to select how the license will be passed to the Sitecore installation. It could be a URL or be specified inline (e.g. copy-paste from the actual file into the field, or, which is more secure, store the value in a protected build variable and add a reference to it here). But it could also be a reference to the file system (published as an artifact of a build), and finally, you can say that your license.xml file is pasted as a parameter into your template parameter file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Section “Security”
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://doc.sitecore.net/sitecore_experience_platform/setting_up_and_maintaining/sitecore_on_azure/analytics/securing_microsoft_azure_resources_for_a_sitecore_deployment" rel="noopener noreferrer"&gt;It is advised&lt;/a&gt; to deny access to the Processing role for everyone and allow access to the Reporting role for Azure only. Also, I checked with Sitecore and by default you do not need to deploy any custom code to these roles, which can speed up your deployment greatly. So, those checkboxes are needed only in case you are not deploying your own custom code on the Processing and Reporting roles and you are not running in ASE (as ASE allows to deploy additional firewalls in front of your web application).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limit access to PRC role by IP&lt;/strong&gt;&lt;br&gt;
This one is a “catchy” setting. If you will check Sitecore templates and SCWDP packages from 8.2 till 9.0.1, you’ll notice that in all fully-fledged deployments you are allowed to set a client IP and client IP mask to limit access to the CM and PRC instances. MsDeploy (SCWDP) packages will have a field for it. By default, it is 0.0.0.0/0.0.0.0. Problem is, this parameter allows you to specify one, and only one IP, which is weird, because you’ll need at least two: 127.0.0.1 to allow applicationInitialization, fired by the Web app itself to complete initialization correctly, and your own IP to access the CM. IP and mask, specified in the relevant parameters, will be passed to the web.config and will be written in the section . And here comes another catch, because the section is defined as follows: “”, and this “clear” node removes any rules defined on the web app in the Networking section of the Azure Web app. So, the only possible solution to protect the PRC web app with only default SCWDP packages deployed (like PRC role + modules) is to inject the parameters IP 127.0.0.1 and mask 255.255.255.255. This will effectively deploy the same limitation on the CM web app, but, at the end, normally you are deploying your own set of config files on the CM instance anyway.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limit access to REP role by IP&lt;/strong&gt;&lt;br&gt;
Following Sitecore best practices, it is recommended to limit access to reporting role by IP, allowing only Azure IPs. However, I see it as this: we need to allow access to reporting role from other roles of deployed solution (CM, CD, PRC) and, possibly, from our own IP(s). Enabling this checkbox will:&lt;br&gt;
a) show additional control for entering IP/Mask in comma-separated string&lt;br&gt;
b) on execution of script – all outgoing IP addresses of other roles will be added to allow list (along with 127.0.0.1 and anything entered in additional control)&lt;br&gt;
Since the REP SCWDP package does not have ipSecurity embedded, this works flawlessly: IP based limitation will be deployed on web app level (you can see it in the Networking section of the Azure web app).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all I wanted to describe about this new extension. Feel free to &lt;a href="//mailto:dobryak.sng@gmail.com"&gt;contact me&lt;/a&gt; for additional questions, push PRs and discuss it on the repository.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>sitecore</category>
    </item>
    <item>
      <title>Saving money with Azure Costs Saver VSTS extension</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Thu, 03 May 2018 08:11:52 +0000</pubDate>
      <link>https://dev.to/akuryan/saving-money-with-azure-costs-saver-vsts-extension-4nb1</link>
      <guid>https://dev.to/akuryan/saving-money-with-azure-costs-saver-vsts-extension-4nb1</guid>
      <description>&lt;p&gt;Since I started working with Azure Web apps I started thinking: how could we optimize budget spending for PaaS? For VMs it is clear, you stop them. VM v1 requires an additional PowerShell command to deprovision them to stop spending money on them, while VM v2 are deprovisioned automatically on stop. But for or Azure SQL databases and Azure Web apps it was not that straightforward.&lt;/p&gt;

&lt;p&gt;So, I took some time to investigate it. The main issue was that I didn’t want to do cumbersome setups like passing resource group names, names of web apps, VMs, SQL databases and their sizes, while keeping track of all changes. Since we are using resource groups to group resources on environment basis, I found out that I could filter all resources from a resource group and find those that could be downsized or deprovisioned (web apps and SQL databases could be downsized, VMs deprovisioned) without hurting the overall application. For example, deleting a Redis cache will require updating the application setup on its recreation.&lt;br&gt;
I feel that using a plain PowerShell script is difficult to share and is just too simple way of solving issues. So I decided to build a VSTS extension for it (TeamCity meta-runner will follow some day too). Check out the result of my &lt;a href="https://marketplace.visualstudio.com/items?itemName=anton-kuryan.AzureCostsSaver#overview"&gt;Azure Costs Saver extension&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;You can find the core of the extension at &lt;a href="https://github.com/akuryan/vsts.extensions/blob/master/AzureCostsSaver/buildtask/budget-saver.ps1"&gt;my GitHub account&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downscales App service plans to S0 size with 1 runner. Currently, it doesn’t have a built-in check if a web app has a staging slot, and you cannot downscale to B0 if you have a staging slot. Also, the B-tier has a size limitation of 10 Gb of disk space, while the S-tier allows up to 50 Gb. Before downscaling, the current parameters are saved on the resource as tags.&lt;/li&gt;
&lt;li&gt;Downscales SQL databases to S0 size, saving the current parameters on the SQL server as tags.&lt;/li&gt;
&lt;li&gt;Downscales Elastic SQL Pools&lt;/li&gt;
&lt;li&gt;Deprovisions VMs (both single and VMSS).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When upscaling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Web apps: the tags are read from the App service plan and they are upscaled back to the original sizes and number of workers written in tags.&lt;/li&gt;
&lt;li&gt;For SQL databases: the tags are read from the SQL server and the SQL databases are upscaled back to the required sizes.&lt;/li&gt;
&lt;li&gt;VMs are started again (those at VMSS as well)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it could be set up
&lt;/h2&gt;

&lt;p&gt;Since running something manually is not the thing I wish to do, let alone several times in a row, I decided to set this up as a VSTS Release; though, the extension could be used in Build as well.&lt;/p&gt;

&lt;p&gt;My release is scaling resources up or down in a resource group, corresponding to 2 environments: T (test) and A (acceptance). The release is scheduled to be automatically created at 13:00 each day from Monday till Friday:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c15HVo-X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2018/11/Azure-Costs-Saver1-600x235.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c15HVo-X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2018/11/Azure-Costs-Saver1-600x235.png" alt="Setting up at Azure DevOps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The release consists of 2 environments: one scales down and is called Minifying, which is executed once after release creation at 22:00 from Monday till Friday; the other is called Reverting and is executed once after release creation at 06:00 from Monday till Friday. This setup ensures that at night and during weekends our resources are at minimal size, while during daytime they are executing at full power.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HILV2RPJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2018/11/Azure-Costs-Saver2-600x275.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HILV2RPJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2018/11/Azure-Costs-Saver2-600x275.png" alt="Setting up at Azure DevOps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each VSTS environment consists of 1 phase with 2 tasks to scale up or down resources from the T and A resource groups; each task is an Azure Cost Saver task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T4wWiY9P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2018/11/Azure-Costs-Saver3-600x187.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T4wWiY9P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dobryak.org/wp-content/uploads/2018/11/Azure-Costs-Saver3-600x187.png" alt="Setting up at Azure DevOps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fields and theirs meaning in task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display name: the name of a task for your convenience&lt;/li&gt;
&lt;li&gt;Azure Connection Type: this set up to be Azure Resource Manager now.&lt;/li&gt;
&lt;li&gt;Azure RM Subscription: a dropdown with subscriptions either accessible to you or configured via VSTS endpoints&lt;/li&gt;
&lt;li&gt;Downscale resources?: a dropdown with a “Yes” and “No” option. If you select “Yes”, the resources will be downscaled and the current sizes (before downscaling) will be written to tags; if  you select “No”, the resources will be upscaled to the sizes stored in tags.&lt;/li&gt;
&lt;li&gt;Resource group name: the name of the resource group where the applicable resources are residing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using my Azure Costs Saver VSTS extension, you can easily save some costs within your Azure subscriptions for non-production environments, only using the full performance when required – fully automated.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>Automating VSTS extensions publishing via TeamCity</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Wed, 18 Apr 2018 15:23:16 +0000</pubDate>
      <link>https://dev.to/akuryan/automating-vsts-extensions-publishing-via-teamcity-371m</link>
      <guid>https://dev.to/akuryan/automating-vsts-extensions-publishing-via-teamcity-371m</guid>
      <description>&lt;p&gt;&lt;strong&gt;First of all, word of warning:&lt;/strong&gt;&lt;br&gt;
Be extra careful, when creating publisher in &lt;a href="https://marketplace.visualstudio.com/" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/&lt;/a&gt; if you are member of several AAD - it is not as straightforward as one could expect. I ended up with one publisher, created from one of my AAD tenants (despite of being authorized at MSFT account - there is a dropdown to select tenant). This is a problem - I have not had VSTS account with these tenant, so I was not able to automate things (I created token, but token was from MSFT user, not AAD tenant user). I ended up requesting support for help - they added my MSFT login to my publisher.&lt;/p&gt;

&lt;p&gt;Now, step by step guide on how to setup Teamcity build.&lt;/p&gt;

&lt;p&gt;1) Install &lt;a href="https://github.com/jonnyzzz/TeamCity.Node" rel="noopener noreferrer"&gt;Teamcity.Node plugin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2) Extension version are patched on Teamcity level (so, developer does not need to keep in mind necessity to bump up those numbers, though, he have to inform DevOps or update Major version in case there is breaking changes)&lt;br&gt;
I am using "VCS labeling" feature of Teamcity to put tag of successful builds and "File content replacer" feature to replace versions in vss-extension.json and task.json. This way, Teamcity will always bump up version in both files before creating extension vsix package.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdobryak.org%2Fwp-content%2Fuploads%2F2018%2F04%2Ffeatures-600x305.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdobryak.org%2Fwp-content%2Fuploads%2F2018%2F04%2Ffeatures-600x305.png" alt="Example of VCS labeling"&gt;&lt;/a&gt;&lt;br&gt;
I was experimenting with several regexps (so, this is not single point of truth, but useful start for your own implementations)&lt;/p&gt;

&lt;p&gt;3) Build steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Node.js NPM from Teamcity.Node plugin
It installs tfx-cli and updates it with following commands:
"install -g tfx-cli
up -g tfx-cli"&lt;/li&gt;
&lt;li&gt;Command line
It invokes tfx to publish my extension
"tfx.cmd extension publish -t %access.token% --manifest-globs vss-extension.json --no-color --no-prompt"&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Some additional findings
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When you need to generate a PAT (personal access token) described &lt;a href="https://docs.microsoft.com/en-us/vsts/extend/publish/command-line?view=vsts" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/vsts/extend/publish/command-line?view=vsts&lt;/a&gt; - do not forget to select "All accounts" in dropdown (as indicated here - &lt;a href="https://social.msdn.microsoft.com/Forums/vstudio/en-US/21073d22-f16e-40d2-9568-3d379e64ff00/why-my-token-cannot-be-used-and-it-always-return-401-error?forum=vsx" rel="noopener noreferrer"&gt;https://social.msdn.microsoft.com/Forums/vstudio/en-US/21073d22-f16e-40d2-9568-3d379e64ff00/why-my-token-cannot-be-used-and-it-always-return-401-error?forum=vsx&lt;/a&gt; ).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix Windows search logic for NPM extension - I was not able to start it via  &lt;code&gt;%AppData%\npm\tfx from Teamcity&lt;/code&gt;, so, I created a symbolic link for &lt;code&gt;%AppData%\npm&lt;/code&gt; for my agents running under System account (&lt;code&gt;mklink D:\data\agentNpm C:\Windows\System32\config\systemprofile\AppData\Roaming\npm /d&lt;/code&gt;) and executed it in command line runner as &lt;code&gt;D:\data\agentNpm\tfx.cmd extension publish -t %access.token% --manifest-globs vss-extension.json --no-color --no-prompt&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>жизнькодерская</category>
      <category>жизньсисадминская</category>
      <category>новости</category>
      <category>automation</category>
    </item>
    <item>
      <title>Trusted self-signed certificate on local Windows machine</title>
      <dc:creator>Anton Kuryan</dc:creator>
      <pubDate>Tue, 13 Mar 2018 13:37:03 +0000</pubDate>
      <link>https://dev.to/akuryan/trusted-self-signed-certificate-on-local-windows-machine-3kdg</link>
      <guid>https://dev.to/akuryan/trusted-self-signed-certificate-on-local-windows-machine-3kdg</guid>
      <description>&lt;p&gt;This is cross-post from &lt;a href="https://dobryak.org/trusted-self-signed-certificate-on-local-windows-machine/"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I created powershell script to automate generation of locally trusted self-signed certificate. It will create root certificate, to be used as signing certificate for future generation of other certificates. &lt;/p&gt;

&lt;p&gt;Please, see script at &lt;a href="https://github.com/akuryan/ConfigurationHelpers/tree/master/Win%20server/sslCertificate-gen"&gt;my repository here&lt;/a&gt;. I hope it will help.&lt;/p&gt;

&lt;p&gt;After certificates have been generated - you can share them with your developers: export as PFX certificate for your domain, and export as CER file certificate from LocalMachine/Root which is issue to "DONOTTRUST-LocalSigningCertificate". Your fellow developer shall import CER file (certificate without key) to "LocalMachine/Root", PFX - to "LocalMachine/My" and can work with self-signed locally trusted certificate happily (NOTE: you could not use this certificate in open, as it would not be trusted by default - that's only for development purposes to save time and money)&lt;/p&gt;

</description>
      <category>новости</category>
    </item>
  </channel>
</rss>
