<?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: David Pazdera</title>
    <description>The latest articles on DEV Community by David Pazdera (@pazdedav).</description>
    <link>https://dev.to/pazdedav</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%2F343800%2Faa59023a-3ea9-42ba-aadc-28516eb98b07.jpeg</url>
      <title>DEV Community: David Pazdera</title>
      <link>https://dev.to/pazdedav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pazdedav"/>
    <language>en</language>
    <item>
      <title>vNext Management and Automation of cloud and hybrid workloads</title>
      <dc:creator>David Pazdera</dc:creator>
      <pubDate>Mon, 13 Mar 2023 17:26:30 +0000</pubDate>
      <link>https://dev.to/pazdedav/vnext-management-and-automation-of-cloud-and-hybrid-workloads-46l5</link>
      <guid>https://dev.to/pazdedav/vnext-management-and-automation-of-cloud-and-hybrid-workloads-46l5</guid>
      <description>&lt;p&gt;This post was created for the &lt;a href="https://sessionize.com/azure-spring-clean-2023/" rel="noopener noreferrer"&gt;Azure Spring Clean 2023&lt;/a&gt; event.&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%2Fwww.azurespringclean.com%2FBit23.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%2Fwww.azurespringclean.com%2FBit23.png" alt="Event logo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you happen to be responsible for running workloads on Azure in production, I bet you are familiar with Microsoft Monitoring Agent, Log Analytics, and Azure Automation services. They have been helping Azure customers with key management disciplines like monitoring, patching, inventory management, and process automation. We simply need to ensure our workloads are "well managed", protected, and watched for any 'abnormal' events that could impact the way our precious applications are available to their end-users.&lt;/p&gt;

&lt;p&gt;With current trends like hybrid-cloud and multi-cloud and ever-changing business needs, our IT landscape is evolving with it, and what worked well and fit our needs a year ago might not be the best fit anymore.&lt;/p&gt;

&lt;p&gt;Don't get me wrong, Azure Automation with its features is still a very robust and battle-tested service, but some specific design and implementation decisions make it difficult to stay relevant in the context of trends I mentioned.&lt;/p&gt;

&lt;p&gt;Examples of such limitations are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependency on Microsoft Monitoring Agent (MMA) and its centralized workspace-level configuration,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/automation/how-to/region-mappings" rel="noopener noreferrer"&gt;Workspace mapping&lt;/a&gt; between Azure Automation and Log Analytics with specific regional pairs,&lt;/li&gt;
&lt;li&gt;support for non-Azure servers (on-premises and Third-Party cloud virtual machines),&lt;/li&gt;
&lt;li&gt;consistent onboarding experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One agent to rule them all
&lt;/h2&gt;

&lt;p&gt;There were several major changes in &lt;strong&gt;Azure Management and Monitoring&lt;/strong&gt; space, but I want to highlight two that are relevant to the topic.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;first one&lt;/strong&gt; is &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/agents/agents-overview" rel="noopener noreferrer"&gt;Azure Monitor Agent&lt;/a&gt; (AMA) that aims (among other things) to consolidate the number of different agents that are available on Azure to monitor IaaS-based workloads and to decentralize its configuration by switching from workspace-level configuration to more distributed and granular Data Collection Rules (DCRs).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AMA collects monitoring data from the guest operating system of Azure and hybrid virtual machines and delivers it to Azure Monitor for use by features, insights, and other services, such as Microsoft Sentinel and Microsoft Defender for Cloud.&lt;/li&gt;
&lt;li&gt;Azure Monitor Agent replaces all of Azure Monitor's legacy monitoring agents. There is a &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/agents/azure-monitor-agent-migration" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; available for moving away from this 'legacy' agent.&lt;/li&gt;
&lt;li&gt;With DCRs you can completely decouple what is being collected (what logs, metrics, and distributed traces), where is it being stored (or streamed out of Azure), and from what sources (VMs). &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Use the DCR &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/agents/azure-monitor-agent-migration-tools#installing-and-using-dcr-config-generator" rel="noopener noreferrer"&gt;generator&lt;/a&gt; to generate DCR resources (rules) from MMA workspace configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;strong&gt;second change&lt;/strong&gt; is the VM extension model that is now common for Azure VMs (with Azure VM Agent) and Arch-connected &lt;a href="https://learn.microsoft.com/en-us/azure/azure-arc/servers/manage-vm-extensions" rel="noopener noreferrer"&gt;machines&lt;/a&gt; (with Connected Machine Agent). &lt;em&gt;VM extensions are small applications that provide post-deployment configuration and automation tasks.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; Several capabilities I will be describing are still in preview, so make sure you understand Azure &lt;a href="https://azure.microsoft.com/support/legal/preview-supplemental-terms/" rel="noopener noreferrer"&gt;terms and conditions&lt;/a&gt; before you decide to use any of these in your production environments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to keep your VMs patched
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Automation Update Management&lt;/strong&gt; &lt;a href="https://learn.microsoft.com/en-us/azure/automation/update-management/overview" rel="noopener noreferrer"&gt;solution&lt;/a&gt; has been around for a long time and it allows for both assessing and deploying missing patches and other types of updates across your Azure and non-Azure server fleet. Onboarding to this service was based on MMA agent, its configuration, and target Log Analytics workspace that was &lt;em&gt;linked&lt;/em&gt; to Azure Automation account that had the Update Management solution enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  vNext
&lt;/h3&gt;

&lt;p&gt;Say hello to 'Azure Update Management Center' (UMC). This new service - currently in Preview - has several key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is a platform native functionality for Azure Compute and Azure Arc for Servers, globally available in all Azure Compute and Azure Arc regions&lt;/li&gt;
&lt;li&gt;there is no dependency on Log Analytics and Azure Automation services&lt;/li&gt;
&lt;li&gt;granular access control at resource level instead of access control at Automation account and Log Analytics workspace level&lt;/li&gt;
&lt;li&gt;improved onboarding experience at scale via Azure Portal and Azure Policy&lt;/li&gt;
&lt;li&gt;ability to enable on-demand and periodic assessments&lt;/li&gt;
&lt;li&gt;configuration of scheduled maintenance (update deployments)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Azure UMC relies on new VM Extensions (&lt;code&gt;LinuxPatchExtension&lt;/code&gt;, &lt;code&gt;WindowsPatchExtension&lt;/code&gt;) that works the same way on Azure VMs and Azure Arc-enabled servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The extension is installed by either Azure VM agent or Connected Machine agent. No manual intervention required.&lt;/li&gt;
&lt;li&gt;This extension is automatically installed when you initiate any UMC operations such as check for updates, install one time update, periodic assessment on your machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For unattended onboarding of Azure VMs, it is required to set the following VM properties:&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;patchSettings.patchMode:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AutomaticByPlatform&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;patchSettings.assessmentMode:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AutomaticByPlatform&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;enableAutomaticUpdates:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the management of a VM is initiated, UMC pushes an extension to the agent. The extensions run locally and interact with the OS. They retrieve assessment information about the status of system updates and initiate download &amp;amp; installation of approved updates.&lt;/p&gt;

&lt;p&gt;Machines assigned to UMC report how up to date they're based on what source they're configured to synchronize with, e.g., WSUS, Microsoft Update, a local or public YUM or APT package repository.&lt;/p&gt;

&lt;p&gt;All &lt;strong&gt;assessment information and update installation results&lt;/strong&gt; are reported to UMC from the extension and are available for analysis with Azure Resource Graph. You can view up to the last seven days of assessment data, and up to the last 30 days of update installation results.&lt;/p&gt;

&lt;p&gt;The following diagram shows all key components and their properties:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0tyud3b6yf68t11yekp.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo0tyud3b6yf68t11yekp.png" alt="A diagram showing how Update Management Center works." width="800" height="832"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Check what OS versions and distros are supported during the preview in the &lt;a href="https://learn.microsoft.com/en-us/azure/update-center/support-matrix?tabs=azurevm%2Cazurevm-os" rel="noopener noreferrer"&gt;support matrix&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The easiest path for enabling automated onboarding is by leveraging three &lt;strong&gt;Azure Policies&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure periodic check for missing system updates on Azure VMs&lt;/strong&gt;: with Modify effect, it updates &lt;code&gt;patchSettings.assessmentMode&lt;/code&gt; property for the deployed VM to be &lt;code&gt;AutomaticByPlatform&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule recurring updates using UMC&lt;/strong&gt;: this DINE policy updates &lt;code&gt;pathcSettings.patchMode&lt;/code&gt; property of the deployed VM to be &lt;code&gt;AutomaticByPlatform&lt;/code&gt; and it deploys a 'Configuration Assignment' resource that links the VM with an existing 'Maintenance Configuration' object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machines should be configured to periodically check for missing system updates&lt;/strong&gt;: this policy checks VM's &lt;code&gt;patchSettings.assessmentMode&lt;/code&gt; property if it has &lt;code&gt;AutomaticByPlatform&lt;/code&gt; value. It could either Audit or Deny the request. You can pick the effect you prefer upon assignment.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;A word of caution: UMC can manage machines currently managed by Azure Automation UM feature without causing any disruptions in your update management process. Microsoft does not recommend customers to migrate from Update Management until UMC goes GA!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Track your software inventory and changes
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Current
&lt;/h3&gt;

&lt;p&gt;When you onboard your VMs to Azure Automation, you are also getting &lt;a href="https://learn.microsoft.com/en-us/azure/automation/change-tracking/overview?tabs=python-2" rel="noopener noreferrer"&gt;Change Tracking and Inventory&lt;/a&gt; features as a bonus, you just need to ensure you enabled them on your Automation account.&lt;/p&gt;

&lt;p&gt;There are several items being tracked, like Windows software, Linux software (packages), Windows and Linux files, Windows registry keys, Windows services, and Linux daemons.&lt;/p&gt;

&lt;p&gt;While there are still some &lt;a href="https://learn.microsoft.com/en-us/azure/automation/change-tracking/overview?tabs=python-2#current-limitations" rel="noopener noreferrer"&gt;limitations&lt;/a&gt; and the supported &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/agents/agents-overview#supported-operating-systems" rel="noopener noreferrer"&gt;OS matrix&lt;/a&gt; is derived from Log Analytics agent, it is a robust service that can help you with e.g., Software Asset Management and configuration drift detection.&lt;/p&gt;
&lt;h3&gt;
  
  
  vNext
&lt;/h3&gt;

&lt;p&gt;I guess it won't surprise you that there is a preview available for Change Tracking and Inventory using AMA agent. &lt;em&gt;After all, we want to get rid of the MMA agent and its dependencies.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The main benefits are derived from the use of AMA agent: multi-homing support, granular management of 'data sources' using DCRs, and compatibility with the Change tracking (CT) extension deployed through the Azure Policy on the client's virtual machine. Once you switch to AMA, the CT extension pushes the software, files, and registry to AMA.&lt;/p&gt;

&lt;p&gt;During the preview, there is an extensive list of &lt;a href="https://learn.microsoft.com/en-us/azure/automation/change-tracking/overview-monitoring-agent#current-limitations" rel="noopener noreferrer"&gt;limitations&lt;/a&gt; you need to take into account.&lt;/p&gt;

&lt;p&gt;If you plan to onboard several VMs at once, consider using  built-in policy initiatives for VMs, VM Scale Sets, or Arc-enabled servers. Simply go to the Azure Portal, open Azure Policy page, and search for initiative definitions that begin with &lt;code&gt;[Preview]: Enable ChangeTracking and Inventory for&lt;/code&gt; string.&lt;/p&gt;

&lt;p&gt;Let's have a closer look at the initiative for Azure VMs and figure out, what individual policies do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creates a User-Assigned Managed Identity and enables it for the VM (eventually you can bring your own / existing UAMI).&lt;/li&gt;
&lt;li&gt;deploys AMA extension for the VM (if it isn't deployed already)&lt;/li&gt;
&lt;li&gt;installs the &lt;code&gt;ChangeTracking&lt;/code&gt; Extension (ChangeTracking-Windows or ChangeTracking-Linux) to enable File Integrity Monitoring (FIM) in Microsoft Defender.&lt;/li&gt;
&lt;li&gt;deploys 'DCR Association' to link the VM to specified DCR to enable ChangeTracking and Inventory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The missing piece is the deployment of a ChangeTracking-specific DCR. You can find the deployment template &lt;a href="https://learn.microsoft.com/en-us/azure/automation/change-tracking/change-tracking-data-collection-rule-creation" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automate your IT processes
&lt;/h2&gt;

&lt;p&gt;Unlike some of the previous areas, this one provides two mature generally available solutions you could choose from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automation runbooks&lt;/li&gt;
&lt;li&gt;Azure Function Apps with PowerShell runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A couple of years ago, I had a session at &lt;em&gt;Nordic Infrastructure Conference&lt;/em&gt; called 'Mortal Compat - Azure Automation vs. Functions', where I compared both services on several levels. I published the slides from this talk at &lt;a href="https://speakerdeck.com/pazdedav/mortal-compat-azure-automation-vs-functions" rel="noopener noreferrer"&gt;Speaker Deck&lt;/a&gt;. Some info is outdated but surprisingly a lot of key facts are still valid.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://speakerdeck.com/pazdedav/mortal-compat-azure-automation-vs-functions" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffiles.speakerdeck.com%2Fpresentations%2F8b37da16bea8442790e15f5a2263db51%2Fslide_0.jpg%3F17781799" height="450" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://speakerdeck.com/pazdedav/mortal-compat-azure-automation-vs-functions" rel="noopener noreferrer" class="c-link"&gt;
          Mortal Compat - Azure Automation vs. Functions - Speaker Deck
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          In this combat, a new version of Azure Functions supporting PowerShell will challenge Azure Automation, a seasoned and widely adopted service for cloud &amp;amp;hellip;
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fd1eu30co0ohy4w.cloudfront.net%2Fassets%2Ffavicon-bdd5839d46040a50edf189174e6f7aacc8abb3aaecd56a4711cf00d820883f47.png" width="512" height="512"&gt;
        speakerdeck.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;I was a huge fan of runbooks and several improvements (like Managed Identity and PowerShell 7 support) rolled out in recent months, but Azure Functions allow you to apply event-driven approach and "plug in" additional components like Logic Apps and Event Grid. For me, this was a game-changer and I switched from runbooks to functions for most scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modern in-guest configuration management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current
&lt;/h3&gt;

&lt;p&gt;The last piece in the puzzle is about being able to configure virtual machines and servers from "the inside" by declaring their &lt;em&gt;desired state&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Azure Automation has a feature called &lt;strong&gt;Automation State Configuration&lt;/strong&gt;. It has been around for a long time, and it allows you to write, manage, and compile PowerShell DSC (Desired State Configuration) configurations.&lt;/p&gt;

&lt;p&gt;You could either enforce the configuration you want or use it as a &lt;em&gt;report-only&lt;/em&gt; tool. And it supports non-Azure VMs as well (running on-premises or in "other clouds").&lt;/p&gt;

&lt;h3&gt;
  
  
  vNext
&lt;/h3&gt;

&lt;p&gt;As you might have guessed, there is a newer and better version of DSC (generally) available, managed by a feature of Azure Policy named guest configuration. The guest configuration service combines features of DSC Extension and Azure Automation State Configuration. Guest configuration also includes hybrid machine support through Arc-enabled servers.&lt;/p&gt;

&lt;p&gt;But wait, there has been a change in the name of this new service. Azure Policy Guest Configuration is now called &lt;strong&gt;Azure Automanage Machine Configuration&lt;/strong&gt;. You didn't see that coming, did you? :)&lt;/p&gt;

&lt;p&gt;Describing in detail how it works would require a separate blog post. You can find more info in the &lt;a href="https://learn.microsoft.com/en-us/azure/governance/machine-configuration/overview" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My own conclusion
&lt;/h2&gt;

&lt;p&gt;All those new management capabilities are in different stages of 'production readiness'; some are in preview, some GA, some available in limited regions or for limited OS versions.&lt;/p&gt;

&lt;p&gt;Is it a suitable time to make the switch? I would say: "Not yet"!&lt;br&gt;
Is this a good time to test them out, build proof-of-concept and get ready to migrate? Absolutely!&lt;/p&gt;

&lt;p&gt;Remember: the clock is ticking, and August 2024 &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/agents/azure-monitor-agent-migration" rel="noopener noreferrer"&gt;deadline&lt;/a&gt; is coming sooner than you think.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>management</category>
      <category>automation</category>
    </item>
    <item>
      <title>Build your own templates for Azure Developer CLI</title>
      <dc:creator>David Pazdera</dc:creator>
      <pubDate>Sat, 17 Dec 2022 17:36:37 +0000</pubDate>
      <link>https://dev.to/pazdedav/build-your-own-templates-for-azure-developer-cli-1opn</link>
      <guid>https://dev.to/pazdedav/build-your-own-templates-for-azure-developer-cli-1opn</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was created for the &lt;a href="https://festivetechcalendar.com/" rel="noopener noreferrer"&gt;Festive Tech Calendar 2022&lt;/a&gt; event.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4j59lgeh7yq8sqn14jev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4j59lgeh7yq8sqn14jev.png" alt="Festive Tech Calendar logo with a Christmas tree" width="577" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Festive greetings eager learners,&lt;/p&gt;

&lt;p&gt;I'm excited to contribute to this year's &lt;strong&gt;Festive Tech Calendar&lt;/strong&gt; with this blog post. As you might have guessed, it will be about the "new CLI kid on the block", Azure Developer CLI.&lt;/p&gt;

&lt;p&gt;Now, there is a pletora of great blog posts and videos about this very topic on the Internet, so why bother? Well, I was thinking about exploring a possibility for using &lt;strong&gt;azd&lt;/strong&gt; not by Devs but by IT Engineers. Sounds like heresy, right? Well, considering how much a traditional IT Pro role has changed with 'infra as code' and 'config as code' practices, cloud adoption, and automation of processes, I don't think it's the case anymore.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pazdedav" rel="noopener noreferrer"&gt;
        pazdedav
      &lt;/a&gt; / &lt;a href="https://github.com/pazdedav/azd-function-pwsh-itpro" rel="noopener noreferrer"&gt;
        azd-function-pwsh-itpro
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Azure Developer CLI template for IT Process automation
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;azd-function-pwsh-itpro&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Azure Developer CLI template for IT Process automation&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/12873988/208253734-ac147a05-33bc-4295-a9ba-c03c4d42a94c.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F12873988%2F208253734-ac147a05-33bc-4295-a9ba-c03c4d42a94c.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hope you will have fun with it!
I certainly had when making this demo :)&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pazdedav/azd-function-pwsh-itpro" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;So, what are we trying to accomplish?&lt;/p&gt;

&lt;h2&gt;
  
  
  The mission
&lt;/h2&gt;

&lt;p&gt;If you worked with Azure and tried to automate some IT processes for some time, you saw a gradual shift from Automation runbooks to PowerShell Functions and event-driven architecture. There are several patterns and use cases but the one we will focus on and try to "azdify" &lt;em&gt;(yep, there is a term for this now)&lt;/em&gt; one particular Azure PowerShell Function that can automate shutdown of Azure VMs.&lt;/p&gt;

&lt;p&gt;Our mission, if you choose to accept it, is to create a reusable &lt;strong&gt;azd template&lt;/strong&gt; that will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provision all required components (Azure Function App, storage account, etc.)&lt;/li&gt;
&lt;li&gt;deploy the app (in this case a PowerShell code)&lt;/li&gt;
&lt;li&gt;enable monitoring, so we could troubleshoot any errors&lt;/li&gt;
&lt;li&gt;create a workflow that will provision &amp;amp; deploy for us automagically&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The recipe
&lt;/h2&gt;

&lt;p&gt;Our main cookbook will be this &lt;a href="https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/make-azd-compatible?pivots=azd-create" rel="noopener noreferrer"&gt;official guide&lt;/a&gt; on Microsoft Learn.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gg28t7hli44dsvmtydy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gg28t7hli44dsvmtydy.png" alt="Microsoft Learn page" width="294" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It doesn't make sense to start from scratch completely, so we could find an existing template (Microsoft or community provided) and cherry-pick elements we could re-use and refactor for our needs. You could either use &lt;a href="https://github.com/topics/azd-templates" rel="noopener noreferrer"&gt;azd-templates topic&lt;/a&gt; on GitHub or use this new &lt;a href="https://azure.github.io/awesome-azd/" rel="noopener noreferrer"&gt;Azwesome AZD&lt;/a&gt; web gallery.&lt;/p&gt;

&lt;p&gt;After a long search, I decided to use &lt;a href="https://github.com/Azure-Samples/todo-csharp-sql-swa-func" rel="noopener noreferrer"&gt;this template&lt;/a&gt; and modify it (quite heavily) for our scenario. &lt;/p&gt;

&lt;h2&gt;
  
  
  A small snag
&lt;/h2&gt;

&lt;p&gt;Since this article is being written while I am playing with the tool, and I don't know the outcome, there is one potential snag: PowerShell is not among officially supported &lt;a href="https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/overview#supported-programming-languages" rel="noopener noreferrer"&gt;languages&lt;/a&gt;. I am still hoping I'll be able to deploy the function with &lt;strong&gt;azd&lt;/strong&gt;, but if not, I could still do it using VS Code or a CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The kitchen
&lt;/h2&gt;

&lt;p&gt;We need a solid dev environment with an IDE and tools that will help us on the way. Here are the specs of my Windows 11 "kitchen":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub CLI v2.20.2&lt;/li&gt;
&lt;li&gt;Azure CLI v2.43.0&lt;/li&gt;
&lt;li&gt;Git v2.39.0&lt;/li&gt;
&lt;li&gt;Windows Terminal v1.15.2875.0&lt;/li&gt;
&lt;li&gt;Bicep CLI v0.12.40.16777&lt;/li&gt;
&lt;li&gt;PowerShell v7.3.0.0&lt;/li&gt;
&lt;li&gt;Visual Studio Code (stable) v1.74.0&lt;/li&gt;
&lt;li&gt;Azure Functions Core Tools v4.x. &lt;em&gt;Read &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cwindows%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools" rel="noopener noreferrer"&gt;this article&lt;/a&gt; to understand, what version you should install and from where.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like using &lt;strong&gt;winget&lt;/strong&gt;, here is your "shopping list":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GitHub.cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.AzureCLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Bicep&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.WindowsTerminal&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Bicep&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.PowerShell&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.VisualStudioCode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;winget&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.AzureFunctionsCoreTools&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We cannot forget about our main hero, &lt;strong&gt;azd&lt;/strong&gt;, right? Since it is in Preview, you can't use &lt;strong&gt;winget&lt;/strong&gt; yet. Pick a &lt;a href="https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd" rel="noopener noreferrer"&gt;method&lt;/a&gt; based on your OS, I am doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;powershell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AllSigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will also need several useful VS Code extensions on top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-azuretools.vscode-azurefunctions
code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-azuretools.vscode-bicep
code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-vscode.powershell
code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-vscode.azure-account
code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-azuretools.azure-dev
code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ms-azuretools.vscode-azureresourcegroups
code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; redhat.vscode-yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also made a few things to ensure consistency between my defaults on GitHub and my local Git configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; init.defaultBranch main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's start baking
&lt;/h2&gt;

&lt;p&gt;Since our goal is to publish our solution on GitHub, we want to start the development in the correct way by creating a project and cloning it locally. You could do it on github.com but I am big fan of "CLI first" approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh repo create azd-function-pwsh-itpro &lt;span class="nt"&gt;--public&lt;/span&gt; &lt;span class="nt"&gt;--clone&lt;/span&gt; &lt;span class="nt"&gt;--add-readme&lt;/span&gt; &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s1"&gt;'Azure Developer CLI template for IT Process automation'&lt;/span&gt;
git pull origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Init
&lt;/h3&gt;

&lt;p&gt;Now when we have the repo created, cloned, and in sync, let's start by scaffolding the repo with a basic "azd template" structure. For the sake of traceability, I will create an Issue and a branch first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh issue create &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"Bootstrap azd template"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Use azd init to scaffold an empty structure for the template."&lt;/span&gt; &lt;span class="nt"&gt;--assignee&lt;/span&gt; &lt;span class="s2"&gt;"@me"&lt;/span&gt;
gh issue develop 1 &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"pazdedav/issue1"&lt;/span&gt; &lt;span class="nt"&gt;--checkout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to initialize the project by using an empty template and set a few parameters like Azure location, environment name (a prefix for the resource group that will be created), and your subscription Id.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd init &lt;span class="nt"&gt;--location&lt;/span&gt; westeurope &lt;span class="nt"&gt;--subscription&lt;/span&gt; 00000000-0000-0000-0000-000000000000 &lt;span class="nt"&gt;--environment&lt;/span&gt; festcal &lt;span class="nt"&gt;--no-prompt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;TIP: The documentation doesn't tell what value should be used for &lt;code&gt;--template&lt;/code&gt; parameter for an empty template. What worked for me was to add &lt;code&gt;--no-prompt&lt;/code&gt; argument instead of specifying a template. Then you won't get any prompt.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice, that there is a new &lt;code&gt;.azure&lt;/code&gt; directory with some config files being generated by the tool. The content doesn't contain any secrets per se, but this directory is added to &lt;code&gt;.gitignore&lt;/code&gt; anyway.&lt;/p&gt;

&lt;p&gt;So far so good, so let's commit all files we got via &lt;code&gt;az init&lt;/code&gt; to our branch, review the changes (ideally done by someone else 🌞) through PR, and merge our code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add azd project scaffold."&lt;/span&gt;
git push origin pazdedav/issue1
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Add azd project scaffold."&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Closes #1"&lt;/span&gt; &lt;span class="nt"&gt;--base&lt;/span&gt; main
gh &lt;span class="nb"&gt;pr &lt;/span&gt;merge &lt;span class="nt"&gt;--merge&lt;/span&gt; &lt;span class="nt"&gt;--subject&lt;/span&gt; &lt;span class="s2"&gt;"Add azd project scaffold."&lt;/span&gt; &lt;span class="nt"&gt;--delete-branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding infrastructure code
&lt;/h3&gt;

&lt;p&gt;In this step, we will add &lt;code&gt;infra&lt;/code&gt; directory and cherry-pick some existing templates from our reference repository (see "The recipe" for more information).&lt;/p&gt;

&lt;p&gt;Let's create a new issue and a branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh issue create &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"Add Bicep IaC"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Add Bicep modules and main template."&lt;/span&gt; &lt;span class="nt"&gt;--assignee&lt;/span&gt; &lt;span class="s2"&gt;"@me"&lt;/span&gt;
gh issue develop 3 &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"pazdedav/issue3"&lt;/span&gt; &lt;span class="nt"&gt;--checkout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: If you wonder, why I skipped "issue 2", I discovered that GitHub includes PRs into the automatic numbering system, so the new issue that was created with &lt;code&gt;gh issue create&lt;/code&gt; command returned "Creating issue in pazdedav/azd-function-pwsh-itpro. &lt;a href="https://github.com/pazdedav/azd-function-pwsh-itpro/issues/3" rel="noopener noreferrer"&gt;https://github.com/pazdedav/azd-function-pwsh-itpro/issues/3&lt;/a&gt;".&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;strong&gt;azd&lt;/strong&gt; project introduced with version 0.2.0-beta.2 a new structure of Bicep infra code based on modules and the template we are using as a reference has this new structure (with &lt;code&gt;app&lt;/code&gt; and &lt;code&gt;core&lt;/code&gt; subdirectories under the &lt;code&gt;infra&lt;/code&gt; directory) aligned already. And since it contains all Azure resources we need for our project, I will simply &lt;a href="https://github.com/Azure-Samples/todo-csharp-sql-swa-func" rel="noopener noreferrer"&gt;download the codebase&lt;/a&gt; as a ZIP file and extract the content of the &lt;code&gt;infra&lt;/code&gt; directory to my project. It should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eiov1rpjj8t6kjciyko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eiov1rpjj8t6kjciyko.png" alt="VS Code screenshot with a repo structure" width="330" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The template includes some additional components we don't need (like a database), so we need to refactor the &lt;code&gt;main.bicep&lt;/code&gt; and &lt;code&gt;main.parameters.json&lt;/code&gt; files to ensure, we will only provision what is required for Azure PowerShell Function. At the same time, I would like to keep the monitoring part in place, so we can get more insights about how the function works (or doesn't work).&lt;/p&gt;

&lt;p&gt;Before we start with refactoring, it's good practice to stage and commit all the copied files to the local repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add infra code from the sample"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would be difficult to write about all changes I have done, so I am sharing the state of three files I changed here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;main. Bicep:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;targetScope = 'subscription'

@minLength(1)
@maxLength(64)
@description('Name of the the environment which is used to generate a short unique hash used in all resources.')
param environmentName string

@minLength(1)
@description('Primary location for all resources')
param location string

// Optional parameters to override the default __azd__ resource naming conventions. Update the main.parameters.json file to provide values. e.g.,:
// "resourceGroupName": {
//      "value": "myGroupName"
// }
param apiServiceName string = ''
param applicationInsightsDashboardName string = ''
param applicationInsightsName string = ''
param appServicePlanName string = ''
param keyVaultName string = ''
param logAnalyticsName string = ''
param resourceGroupName string = ''
param storageAccountName string = ''

@description('Id of the user or app to assign application roles')
param principalId string = ''

var abbrs = loadJsonContent('./abbreviations.json')
var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
var tags = { 'azd-env-name': environmentName }

// Organize resources in a resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'
  location: location
  tags: tags
}

// The application backend
module api './app/api.bicep' = {
  name: 'api'
  scope: rg
  params: {
    name: !empty(apiServiceName) ? apiServiceName : '${abbrs.webSitesFunctions}api-${resourceToken}'
    location: location
    tags: tags
    applicationInsightsName: monitoring.outputs.applicationInsightsName
    appServicePlanId: appServicePlan.outputs.id
    keyVaultName: keyVault.outputs.name
    storageAccountName: storage.outputs.name
    appSettings: {
    }
  }
}

// Give the API access to KeyVault
module apiKeyVaultAccess './core/security/keyvault-access.bicep' = {
  name: 'api-keyvault-access'
  scope: rg
  params: {
    keyVaultName: keyVault.outputs.name
    principalId: api.outputs.SERVICE_API_IDENTITY_PRINCIPAL_ID
  }
}

// Create an App Service Plan to group applications under the same payment plan and SKU
module appServicePlan './core/host/appserviceplan.bicep' = {
  name: 'appserviceplan'
  scope: rg
  params: {
    name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}'
    location: location
    tags: tags
    sku: {
      name: 'Y1'
      tier: 'Dynamic'
    }
  }
}

// Backing storage for Azure functions backend API
module storage './core/storage/storage-account.bicep' = {
  name: 'storage'
  scope: rg
  params: {
    name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}'
    location: location
    tags: tags
  }
}

// Store secrets in a keyvault
module keyVault './core/security/keyvault.bicep' = {
  name: 'keyvault'
  scope: rg
  params: {
    name: !empty(keyVaultName) ? keyVaultName : '${abbrs.keyVaultVaults}${resourceToken}'
    location: location
    tags: tags
    principalId: principalId
  }
}

// Monitor application with Azure Monitor
module monitoring './core/monitor/monitoring.bicep' = {
  name: 'monitoring'
  scope: rg
  params: {
    location: location
    tags: tags
    logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}'
    applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}'
    applicationInsightsDashboardName: !empty(applicationInsightsDashboardName) ? applicationInsightsDashboardName : '${abbrs.portalDashboards}${resourceToken}'
  }
}

// App outputs
output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString
output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint
output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name
output AZURE_LOCATION string = location
output AZURE_TENANT_ID string = tenant().tenantId
output REACT_APP_API_BASE_URL string = api.outputs.SERVICE_API_URI
output REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;main.parameters.json:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"environmentName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${AZURE_ENV_NAME}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${AZURE_LOCATION}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"principalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${AZURE_PRINCIPAL_ID}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;api.bicep:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param name string
param location string = resourceGroup().location
param tags object = {}

param allowedOrigins array = []
param applicationInsightsName string = ''
param appServicePlanId string
param appSettings object = {}
param keyVaultName string
param serviceName string = 'api'
param storageAccountName string

module api '../core/host/functions.bicep' = {
  name: '${serviceName}-functions-powershell-module'
  params: {
    name: name
    location: location
    tags: union(tags, { 'azd-service-name': serviceName })
    allowedOrigins: allowedOrigins
    alwaysOn: false
    appSettings: appSettings
    applicationInsightsName: applicationInsightsName
    appServicePlanId: appServicePlanId
    keyVaultName: keyVaultName
    runtimeName: 'powershell'
    runtimeVersion: '7.2'
    storageAccountName: storageAccountName
    scmDoBuildDuringDeployment: false
  }
}

output SERVICE_API_IDENTITY_PRINCIPAL_ID string = api.outputs.identityPrincipalId
output SERVICE_API_NAME string = api.outputs.name
output SERVICE_API_URI string = api.outputs.uri
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want to validate my infra code by running the following checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az bicep build &lt;span class="nt"&gt;--file&lt;/span&gt; infra/main.bicep
az deployment sub validate &lt;span class="nt"&gt;--location&lt;/span&gt; westeurope &lt;span class="nt"&gt;--template-file&lt;/span&gt; infra/main.bicep &lt;span class="nt"&gt;--parameters&lt;/span&gt; &lt;span class="nv"&gt;environmentName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;festcal &lt;span class="nv"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;westeurope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one "transpiles" our Bicep templates into a single JSON ARM deployment template and displays all errors and warnings found by Bicep linter. The second command checks our infra code against ARM API (also known as &lt;em&gt;pre-flight&lt;/em&gt;). You need to be signed in to Azure in the console for the second command to work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Since our Bicep code creates a Resource Group and provisions all resources, we are using 'subscription' as a scope for the deployment.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn't get any warnings or errors, so we could try to create our environment with &lt;strong&gt;azd&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd provision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilà. We have our environment completely provisioned up in Azure:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7b5huhhrssjtkf3yzn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7b5huhhrssjtkf3yzn9.png" alt="Command line output with a successful provisioning" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: the last __azd&lt;/em&gt;_ command also updates the &lt;code&gt;.env&lt;/code&gt; file in &lt;code&gt;.azure&lt;/code&gt; directory with all Bicep outputs we declared in the code but don't worry all files under the &lt;code&gt;.azure&lt;/code&gt; directory are excluded from Git tracking, so they won't be committed and pushed to origin._&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now let's commit, push, and merge everything to GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"Refactor infra code."&lt;/span&gt;
git push origin pazdedav/issue3
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Add infrastructure code to the template."&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Closes #3"&lt;/span&gt; &lt;span class="nt"&gt;--base&lt;/span&gt; main
gh &lt;span class="nb"&gt;pr &lt;/span&gt;merge &lt;span class="nt"&gt;--merge&lt;/span&gt; &lt;span class="nt"&gt;--subject&lt;/span&gt; &lt;span class="s2"&gt;"Add infra code to the template."&lt;/span&gt; &lt;span class="nt"&gt;--delete-branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding PowerShell function
&lt;/h3&gt;

&lt;p&gt;Now we can scaffold our functions project (using Azure Functions Core Tools), add some code, and see if &lt;strong&gt;azd&lt;/strong&gt; can deploy it or not.&lt;/p&gt;

&lt;p&gt;Let's create a new issue and a branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh issue create &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"Add PowerShell Function"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Scaffold a new function project and add PowerShell code."&lt;/span&gt; &lt;span class="nt"&gt;--assignee&lt;/span&gt; &lt;span class="s2"&gt;"@me"&lt;/span&gt;
gh issue develop 5 &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"pazdedav/issue5"&lt;/span&gt; &lt;span class="nt"&gt;--checkout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to keep our project neat and structured, so the function code should be placed in this path: &lt;code&gt;src/api&lt;/code&gt;. Let's create this path and initialize the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;src&lt;span class="se"&gt;\a&lt;/span&gt;pi
&lt;span class="nb"&gt;cd &lt;/span&gt;src&lt;span class="se"&gt;\a&lt;/span&gt;pi
func init &lt;span class="nt"&gt;--worker-runtime&lt;/span&gt; powershell &lt;span class="nt"&gt;--managed-dependencies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see, what files were generated by &lt;code&gt;func init&lt;/code&gt; in our editor:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F198jsdu45di9zjez88qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F198jsdu45di9zjez88qq.png" alt="VS Code screenshot with a repo structure" width="323" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For local functions development, you could either install a local storage emulator, or update &lt;code&gt;local.settings.json&lt;/code&gt; file to use an existing storage account in Azure. I don't want to install even more tools to my development machine, so I will use the storage account we provisioned a while ago. Simply run the following command with the Function App name you got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;func azure functionapp fetch-app-settings func-api-c4dvlakzaax5o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need some actual PowerShell function code. I didn't want to use the boilerplate code, so I was looking for a good example for our "IT Pros" scenario in the Serverless Library, and I chose &lt;a href="https://serverlesslibrary.net/sample/24000d65-f927-4f6a-bdcc-e20b112e8fa9" rel="noopener noreferrer"&gt;this one&lt;/a&gt; created by &lt;a href="https://github.com/eamonoreilly" rel="noopener noreferrer"&gt;Eamon O'Reilly&lt;/a&gt;. It's a timer-based function that can stop Azure VMs on a schedule.&lt;/p&gt;

&lt;p&gt;First, we need to create a new function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;func new &lt;span class="nt"&gt;--name&lt;/span&gt; StopVMOnTimer &lt;span class="nt"&gt;--template&lt;/span&gt; &lt;span class="s2"&gt;"Timer trigger"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will need Az modules to work. Luckily, PowerShell Functions have a feature called "managed dependencies" that can download required modules for us. All we need is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check in the &lt;a href="https://www.powershellgallery.com/packages/Az/9.2.0" rel="noopener noreferrer"&gt;PowerShell Gallery&lt;/a&gt; what version we want to add. &lt;em&gt;Currently, it is v9.2.0.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;edit the &lt;code&gt;requirements.psd1&lt;/code&gt; file, so it looks like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This file enables modules to be automatically managed by the Functions service.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# See https://aka.ms/functionsmanageddependency for additional information.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. &lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# To use the Az module in your function app, please uncomment the line below.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s1"&gt;'Az'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'9.*'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on how often we want this function to run, we might want to update the &lt;code&gt;schedule&lt;/code&gt; key in &lt;code&gt;function.json&lt;/code&gt; configuration file. I want to shutdown my VMs every day at 7 PM, so this is how it looks in my case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bindings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Timer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"timerTrigger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"in"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 0 19 * * *"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's replace the boilerplate code in &lt;code&gt;run.ps1&lt;/code&gt; file with the &lt;a href="https://github.com/eamonoreilly/StartStopPowerShellFunction/blob/master/StopVMOnTimer/run.ps1" rel="noopener noreferrer"&gt;same file&lt;/a&gt; from Eamon's repo, so it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Input bindings are passed in via param block.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Timer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Specify the VMs that you want to stop. Modify or comment out below based on which VMs to check.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$VMResourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-vms-rg"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;#$VMName = "ContosoVM1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$TagName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AutomaticallyStop"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Stop on error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="bp"&gt;$Error&lt;/span&gt;&lt;span class="n"&gt;ActionPreference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'stop'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Check if managed identity has been enabled and granted access to a subscription, resource group, or resource&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$AzContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-AzContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AzContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscription&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;Throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Managed identity is not enabled for this app or it has not been granted access to any Azure resources. Please see https://docs.microsoft.com/en-us/azure/app-service/overview-managed-identity for additional details."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# Get a single vm, vms in a resource group, or all vms in the subscription&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMResourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Information&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Getting VM in resource group "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMResourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" and VMName "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$VMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-AzVM&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="nv"&gt;$VMResourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMName&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;elseif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMResourceGroupName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Information&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Getting all VMs in resource group "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMResourceGroupName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$VMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-AzVM&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="nv"&gt;$VMResourceGroupName&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Information&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Getting all VMs in the subscription"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$VMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-AzVM&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# Check if VM has the specified tag on it and filter to those.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TagName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$VMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Tags&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TagName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# Stop the VM if it is running&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$ProcessedVMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@()&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$VirtualMachine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$VM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-AzVM&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="nv"&gt;$VirtualMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VirtualMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Status&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$VM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Statuses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'PowerState/running'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;Write-Information&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Stopping VM "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VirtualMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;$ProcessedVMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VirtualMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;Stop-AzVM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$VirtualMachine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&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;-AsJob&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write-Information&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# Sleep here a few seconds to make sure that the command gets processed before the script ends&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ProcessedVMs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Start-Sleep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Message&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I made two small modifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;updated the &lt;code&gt;$VMResourceGroupName&lt;/code&gt; variable to &lt;code&gt;test-vms-rg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;uncommented the &lt;code&gt;$TagName&lt;/code&gt; variable, so only VMs with this resource tag will eventually be stopped&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test environment in Azure
&lt;/h2&gt;

&lt;p&gt;If we want to test this function, we need to have an actual environment with a Resource Group (called &lt;code&gt;test-vms-rg&lt;/code&gt;) and some VMs with that &lt;code&gt;AutomaticallyStop&lt;/code&gt; tag. &lt;/p&gt;

&lt;p&gt;We also need to make sure our function can perform "VM Stop" operations using PowerShell. Since we are using Managed Identity and our &lt;code&gt;azd provision&lt;/code&gt; step created a system-assigned managed identity for us, it is only a matter of grabbing correct objectId and do a role assignment. &lt;/p&gt;

&lt;p&gt;Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test-vms-rg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;westeurope&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$assigneeObjectId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--display-name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;func-api-c4dvlakzaax5o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[0].id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tsv&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assignment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--assignee-object-id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$assigneeObjectId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--assignee-principal-type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ServicePrincipal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Virtual Machine Contributor"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-vms-rg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ideally, we should have at least one VM in our Resource Group to test the functionality. Let's create one quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az vm create &lt;span class="nt"&gt;--resource-group&lt;/span&gt; test-vms-rg &lt;span class="nt"&gt;--name&lt;/span&gt; myVM01 &lt;span class="nt"&gt;--image&lt;/span&gt; UbuntuLTS &lt;span class="nt"&gt;--admin-username&lt;/span&gt; azureuser &lt;span class="nt"&gt;--generate-ssh-keys&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;AutomaticallyStop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test locally
&lt;/h2&gt;

&lt;p&gt;One of many Azure Functions benefits is the ability to test them locally before you publish them to Azure. For all functions other than HTTP and Event Grid triggers, you can test your functions locally using REST by calling a special endpoint called an &lt;em&gt;administration endpoint&lt;/em&gt;. It has the following Uri format: &lt;code&gt;http://localhost:{port}/admin/functions/{function_name}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Calling this endpoint with an HTTP POST request on the local server triggers the function.&lt;/p&gt;

&lt;p&gt;Let's run the function and use &lt;code&gt;Invoke-WebRequest&lt;/code&gt; cmdlet to trigger the function. We will need to open two windows in Windows Terminal for this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First terminal session:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;func&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Second terminal session:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:7071/admin/functions/StopVMOnTimer"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For some reason, I got &lt;strong&gt;"400 (Bad Request)"&lt;/strong&gt; response and the log running in the first terminal did not provide any clues, what I am missing.&lt;/p&gt;

&lt;p&gt;We will still try to deploy this function to Azure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update azd manifest and deploy
&lt;/h2&gt;

&lt;p&gt;Ok, let's update the &lt;code&gt;azure.yaml&lt;/code&gt; file and check, if we can use &lt;code&gt;azd deploy&lt;/code&gt; or not:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax852kkhxanxvjwobzd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax852kkhxanxvjwobzd0.png" alt="Screenshot with azure.yaml file content" width="800" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslzo8rhdj1y5tkvpf2k9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslzo8rhdj1y5tkvpf2k9.png" alt="Error message from azd in the command line" width="800" height="49"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember, what I wrote earlier about a potential snag? It seems we  need to wait for PowerShell language support for Functions.&lt;/p&gt;

&lt;p&gt;For the time being, we will use &lt;code&gt;func&lt;/code&gt; CLI to publish our function to Azure, more specifically with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;func azure functionapp publish func-api-c4dvlakzaax5o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp423berld9no7dn3j37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp423berld9no7dn3j37.png" alt="CLI screenshot with a successful Azure Function deployment" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I feel good about the progress we made, so let's commit, push, and merge our code again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add function code."&lt;/span&gt;
git push origin pazdedav/issue5
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Add PowerShell function code to the template."&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Closes #5"&lt;/span&gt; &lt;span class="nt"&gt;--base&lt;/span&gt; main
gh &lt;span class="nb"&gt;pr &lt;/span&gt;merge &lt;span class="nt"&gt;--merge&lt;/span&gt; &lt;span class="nt"&gt;--subject&lt;/span&gt; &lt;span class="s2"&gt;"Add application code to the template."&lt;/span&gt; &lt;span class="nt"&gt;--delete-branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure a GitHub workflow
&lt;/h2&gt;

&lt;p&gt;One missing step is to enable integration with GitHub Actions, so we can automate both the provisioning and deployment of our solution.&lt;/p&gt;

&lt;p&gt;Since we were tracking our work from GH issues, through PRs, to merges before, we won't skip this step for our last exercise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh issue create &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"Add GitHub workflow."&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Add GitHub workflow yaml file."&lt;/span&gt; &lt;span class="nt"&gt;--assignee&lt;/span&gt; &lt;span class="s2"&gt;"@me"&lt;/span&gt;
gh issue develop 7 &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"pazdedav/issue7"&lt;/span&gt; &lt;span class="nt"&gt;--checkout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what we will do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a &lt;code&gt;.github&lt;/code&gt; directory with a &lt;code&gt;workflows&lt;/code&gt; subdirectory&lt;/li&gt;
&lt;li&gt;copy the &lt;strong&gt;azure-dev.yml&lt;/strong&gt; file from the &lt;a href="https://github.com/Azure-Samples/todo-csharp-sql-swa-func/blob/main/.github/workflows/azure-dev.yml" rel="noopener noreferrer"&gt;sample repo&lt;/a&gt;, we have been working with
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/Azure-Samples/todo-csharp-sql-swa-func/raw/main/.github/workflows/azure-dev.yml"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$workflowFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;github&lt;/span&gt;&lt;span class="nx"&gt;\workflows&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".github\workflows"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"azure-dev.yml"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"file"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$workflowFile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow requires you to create a couple of secrets, before it can run successfully, as you can see in the workflow file. We could create them manually together with a Service Principal like this (&lt;em&gt;don't do it like this, keep on reading&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;gh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\.azure\festcal\.env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create-for-rbac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;azd-festcal-temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;contributor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--scopes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/subscriptions/0c310ab4-e68f-45e7-b7e2-72fbbb32e891&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gh_spn.json&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$ghSpnValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\gh_spn.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Raw&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;gh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AZURE_CREDENTIALS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ghSpnValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\gh_spn.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunatelly, &lt;strong&gt;azd&lt;/strong&gt; can do everything for us, we simply need to run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd pipeline config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, everything is set:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faaw610jkxnn765vnf6oe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faaw610jkxnn765vnf6oe.png" alt="CLI screenshot showing what azd pipeline config does" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We could say "Y" to get the pipeline started, but I'd like to do it manually, so I choose "n".&lt;/p&gt;

&lt;p&gt;Let's close this last exercise with a couple of steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add GitHub workflow file."&lt;/span&gt;
git push origin pazdedav/issue7
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Add GitHub workflow to the template."&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Closes #7"&lt;/span&gt; &lt;span class="nt"&gt;--base&lt;/span&gt; main
gh &lt;span class="nb"&gt;pr &lt;/span&gt;merge &lt;span class="nt"&gt;--merge&lt;/span&gt; &lt;span class="nt"&gt;--subject&lt;/span&gt; &lt;span class="s2"&gt;"Add azure-dev.yml to the template."&lt;/span&gt; &lt;span class="nt"&gt;--delete-branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, our pipeline run will fail due to the missing support for PowerShell in &lt;strong&gt;azd&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlpowlpu2zi5b673fv8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlpowlpu2zi5b673fv8i.png" alt="Screenshot from GitHub Actions showing a failed workflow run" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We could say this experiment wasn't strictly speaking successful. Our pipeline is failing, and we couldn't get the local test running.&lt;/p&gt;

&lt;p&gt;On the other hand, we (hopefully) learned a lot about how &lt;strong&gt;azd&lt;/strong&gt; works under the hood with a bunch of CLI examples using &lt;code&gt;git&lt;/code&gt;, &lt;code&gt;gh&lt;/code&gt;, &lt;code&gt;func&lt;/code&gt;, PowerShell, and &lt;code&gt;az&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you managed to get to the end of this post, I hope it wasn't a waste of time for you.&lt;/p&gt;

&lt;p&gt;Have a great Christmas holiday and all the best in 2023.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;If you followed along and created resources in Azure, it's a good idea to clean things up to avoid an unexpected bill from Microsoft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azd&lt;/strong&gt; has a simple command for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might be prompted a few times to confirm your plan but in the end you should see something like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxajaka88eqfel5sr1i3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxajaka88eqfel5sr1i3h.png" alt="Screenshot from CLI showing azd down command" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other useful resources
&lt;/h2&gt;

&lt;p&gt;For those of you who want to learn more about this awesome tool, here is a couple of resources I found especially useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/playlist?list=PL5rXtjltSImRXQiR02pZvj__Isgn1q34Y" rel="noopener noreferrer"&gt;Azure Developer CLI&lt;/a&gt; YouTube channel&lt;/li&gt;
&lt;li&gt;Series of &lt;a href="https://dev.to/lechnerc77/the-azure-developer-cli-compatibility-journey-for-an-azure-functions-project-3mc1"&gt;blog posts&lt;/a&gt; on Dev.to published by Christian Lechner&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>beginners</category>
      <category>frontend</category>
      <category>backend</category>
    </item>
    <item>
      <title>Azure Native Infrastructure as Code for Advanced Practitioners</title>
      <dc:creator>David Pazdera</dc:creator>
      <pubDate>Thu, 26 Nov 2020 17:51:12 +0000</pubDate>
      <link>https://dev.to/pazdedav/azure-native-infrastructure-as-code-for-advanced-practitioners-4gpk</link>
      <guid>https://dev.to/pazdedav/azure-native-infrastructure-as-code-for-advanced-practitioners-4gpk</guid>
      <description>&lt;p&gt;This post was created for the &lt;strong&gt;&lt;a href="https://festivetechcalendar.com/" rel="noopener noreferrer"&gt;Festive Tech Calendar 2020&lt;/a&gt;&lt;/strong&gt; event and it is a narrative to the talk I gave at &lt;em&gt;Infrastructure as Code User Group Oslo&lt;/em&gt; &lt;a href="https://www.meetup.com/Infrastructure-As-Code-User-Group-Oslo/events/273116797/" rel="noopener noreferrer"&gt;meetup&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvwnfkl8t7peqpr01li9e.jpg" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvwnfkl8t7peqpr01li9e.jpg" alt="festive-tech-calendar" width="300" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The purpose is to demonstrate a complex end-to-end scenario for using advanced capabilities of Azure Resource Manager language (like template specs, deployment scopes, functions) and the new ARM DSL language called Bicep. It also tries to demystify a popular opinion that the ARM language is "just a JSON".&lt;/p&gt;

&lt;p&gt;All code samples and other content could be found in my repo on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pazdedav" rel="noopener noreferrer"&gt;
        pazdedav
      &lt;/a&gt; / &lt;a href="https://github.com/pazdedav/iac-meetup-arm" rel="noopener noreferrer"&gt;
        iac-meetup-arm
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Repository for Infrastructure as Code meetup, demonstrating a complex end-to-end scenario for using advanced capabilities of Azure Resource Manager language (like templateSpecs, deployment scopes, deploymentScripts) and the new ARM DSL called Bicep.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Story
&lt;/h2&gt;

&lt;p&gt;Our story begins when Contoso Corp. (a fictious company) announces their new business strategy with digital transformation, customer obsession, and lean as key pillars. This had a profound impact on both IT and software development organizations at Contoso. Old habits and fixed mindset are no longer acceptable, there is a high pressure on everybody to invest their time to learn and grow.&lt;/p&gt;

&lt;p&gt;Our first protagonist, &lt;strong&gt;Meghan&lt;/strong&gt; is a senior engineer who has been with the company for five years. &lt;strong&gt;Mat&lt;/strong&gt; on the other hand is part of a newly formed &lt;em&gt;Central Cloud Team&lt;/em&gt;. In his new Cloud Engineer role, he is focusing on security, compliance, and building a 'secure and compliant' cloud platform that will be used by internal teams. He has a strong infrastructure background with two decades of experience managing networks and server infrastructure.&lt;/p&gt;

&lt;p&gt;At the beginning of our story, a new &lt;strong&gt;cloud governance model&lt;/strong&gt; has been defined by key stakeholders. This model is partly a re-write of Contoso's existing security policies (adopted for the cloud) that was combined with new regulatory requirements that were introduced to the industry.&lt;/p&gt;

&lt;p&gt;Mat's team has an important task now. They must ensure there are reliable controls and audit capabilities enforced on their entire cloud environment, so all systems, apps, and services deployed to the cloud are compliant from Day 1.&lt;/p&gt;

&lt;p&gt;Meghan's team has been developing a new application when this change of policies is announced. There is a growing fear that this new policy framework will slow down their progress and introduce many blockers. These concerns were escalated to VP of App Development and it was decided that &lt;strong&gt;the Central Cloud Team need to find out a way to support developers while maintaining the governance model&lt;/strong&gt; as required by the business and industry regulators.&lt;/p&gt;

&lt;p&gt;Apart from our two main protagonists, there are other heroes in this story :) :&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9t1epzudp16cl2erqlfv.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9t1epzudp16cl2erqlfv.png" alt="true-heroes" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Policy as Code
&lt;/h2&gt;

&lt;p&gt;Mat's team was busy defining what controls and audit mechanisms they should use in the new governance model. They have done a few experiments, tested several technologies and platform features on Azure, and they decided to use &lt;strong&gt;Azure Policies&lt;/strong&gt; together with &lt;strong&gt;Management Groups&lt;/strong&gt; to apply them at scale, so they could ensure 'Day 1 compliance' for all new subscriptions as well as the existing ones.&lt;/p&gt;

&lt;p&gt;They both defined and assigned all their policies in the Azure &lt;a href="https://portal.azure.com/#blade/Microsoft_Azure_Policy/PolicyMenuBlade/Overview" rel="noopener noreferrer"&gt;Portal&lt;/a&gt; but they wanted to adopt 'Policy as code' practice to automate definition and assignment workflows and improve change tracking.&lt;/p&gt;

&lt;p&gt;The team learned about a new &lt;a href="https://docs.microsoft.com/en-us/azure/governance/policy/tutorials/policy-as-code-github?WT.mc_id=Portal-AzureTfsExtension#export-azure-policy-objects-from-the-azure-portal" rel="noopener noreferrer"&gt;feature&lt;/a&gt;, allowing them to export Azure Policy objects (both definitions and assignments) from the Portal to GitHub repo. This feature also creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a workflow using GitHub Actions that can automate 'deployments' of any changes made in the repo&lt;/li&gt;
&lt;li&gt;a GitHub secret for authenticating to Azure using a service principal&lt;/li&gt;
&lt;li&gt;a service principal that was granted the 'Resource Policy Contributor' role, so it can change policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mat's team followed the process from the documentation and exported all their policies and assignments to &lt;code&gt;src/policies/&lt;/code&gt; folder in their &lt;a href="https://github.com/pazdedav/iac-meetup-arm/tree/master/src/policies" rel="noopener noreferrer"&gt;repo&lt;/a&gt;. The workflow YML file can be found &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/.github/workflows/manage-azure-policy-07a68ce5.yml" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The workflow is by default configured to be triggered manually, but this can be changed.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz3u4dwtdyrlhz9dzcn4.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz3u4dwtdyrlhz9dzcn4.png" alt="run_log" width="800" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They also published the list of applied policies in their &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/.github/workflows/manage-azure-policy-07a68ce5.yml" rel="noopener noreferrer"&gt;Wiki&lt;/a&gt; to make it more readable for non-technical stakeholders.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyc9zcsfn9ex53mpoi2d.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyc9zcsfn9ex53mpoi2d.png" alt="wiki_policies" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Template Library
&lt;/h2&gt;

&lt;p&gt;Being transparent about what policies are applied in the cloud environment (and how they can impact deployments done by internal teams) is a good start, but the Central Cloud Team &lt;strong&gt;wanted to increase productivity of app development teams&lt;/strong&gt; even more, so they decided to introduce a new service called &lt;em&gt;Template Library&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The goal was simple: provide a repository of reusable, dynamic, and security-hardened resource manager templates, fully compliant with the governance model, so they could be used as artifacts for building more complex deployments.&lt;/p&gt;

&lt;p&gt;The idea of having reusable artifacts (single resource 'linked' templates that could be referenced from the main template) isn't new, but there are several challenges for adopting this technique.&lt;/p&gt;

&lt;p&gt;Mat and his team heard about &lt;strong&gt;template specs&lt;/strong&gt;, a new capability in Azure Resource Manager that allows developers to refer to these artifacts in form of &lt;code&gt;ResourceId&lt;/code&gt; that could be referenced in the same way as linked templates but without the need to stage them first to e.g. Azure Blob storage or expose them in a public repository.&lt;/p&gt;

&lt;p&gt;The team defined the following &lt;strong&gt;requirements&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all template specs will be stored in our GitHub &lt;a href="https://github.com/pazdedav/iac-meetup-arm/tree/master/src/template-lib-code" rel="noopener noreferrer"&gt;repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;all changes (to already published versions or new versions) will be pushed to Azure using a workflow after a peer review&lt;/li&gt;
&lt;li&gt;all developers will be granted a Reader role in the Resource Group where all specs are stored&lt;/li&gt;
&lt;li&gt;develop an &lt;a href="https://github.com/pazdedav/iac-meetup-arm/wiki/Template-Library#onboarding-your-service-principals" rel="noopener noreferrer"&gt;automated way&lt;/a&gt; to onboard 'service principals' to the Library for teams that use them in their workflows (pipelines)&lt;/li&gt;
&lt;li&gt;publish a &lt;a href="https://github.com/pazdedav/iac-meetup-arm/wiki/Template-Library" rel="noopener noreferrer"&gt;guide&lt;/a&gt; for our internal customers on how to use this Library, so they can start using it&lt;/li&gt;
&lt;li&gt;if a spec uses a JSON object as a parameter, publish a &lt;a href="https://github.com/pazdedav/iac-meetup-arm/wiki/Templates-(definitions)" rel="noopener noreferrer"&gt;definition&lt;/a&gt; on Wiki that explains the structure of the parameters.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Onboarding workflow
&lt;/h3&gt;

&lt;p&gt;The team defined the following workflow, allowing dev teams to onboard their service principal (typically used in Azure DevOps, GitHub Actions, or any other CI/CD pipelines) to the Library:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new branch in the Central Cloud Team's GitHub repo.&lt;/li&gt;
&lt;li&gt;Modify &lt;code&gt;contoso-template-library.prod.parameters.json&lt;/code&gt; &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/src/template-lib-iac/contoso-template-library.prod.parameters.json" rel="noopener noreferrer"&gt;file&lt;/a&gt; by adding &lt;code&gt;objectId&lt;/code&gt; of your Service Principal as new array member of &lt;code&gt;servicePrincipals&lt;/code&gt; parameter value. You can create your SP by using e.g. &lt;code&gt;az ad sp create-for-rbac --name "{sp-name}" --sdk-auth&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;Create a Pull Request and specify your 'projectId' and 'costPollId' in the description.&lt;/li&gt;
&lt;li&gt;When the request is reviewed and approved by our team, it will trigger a &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/.github/workflows/template-library-iam.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; that will assign a Reader role to your SP in the context of the Resource Group hosting the Template Library.&lt;/li&gt;
&lt;/ol&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjeqmcu4xwo63g4wy2orn.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjeqmcu4xwo63g4wy2orn.png" alt="sp_onboarding_library" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The template &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/src/template-lib-iac/contoso-template-library.template.json" rel="noopener noreferrer"&gt;file&lt;/a&gt; - &lt;code&gt;contoso-template-library.template.json&lt;/code&gt; - is a good demonstration of several ARM language features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;deployment scopes&lt;/strong&gt; - allowing to create a Resource Group at the subscription level and assign an RBAC role (as a deployment on Resource Group level), all in one template&lt;/li&gt;
&lt;li&gt;using &lt;strong&gt;comments&lt;/strong&gt; in several places in the file (ARM allows both single-line and multi-line comments)&lt;/li&gt;
&lt;li&gt;using &lt;strong&gt;functions&lt;/strong&gt; like &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;contains&lt;/code&gt;, &lt;code&gt;concat&lt;/code&gt;, &lt;code&gt;copy&lt;/code&gt;, &lt;code&gt;resourceId&lt;/code&gt;, &lt;code&gt;guid&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Using template specs
&lt;/h3&gt;

&lt;p&gt;Now we will switch to Meghan and learn how she used the Template Library provided by Mat's team in her workflow.&lt;/p&gt;

&lt;p&gt;First, she used the onboarding procedure and added the service principle used by her team to the Library. Her Pull Request was approved, so she could modify her deployment &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/src/template-example/serviceOne.template.json" rel="noopener noreferrer"&gt;template&lt;/a&gt; - &lt;code&gt;serviceOne.template.json&lt;/code&gt; - and referenced 'contosoStorage' spec. Her team is using GitHub &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/.github/workflows/serviceOne-deployment.yml" rel="noopener noreferrer"&gt;workflow&lt;/a&gt; to deploy the template to Azure. &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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpp5dqa7dzs17rc30om9.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkpp5dqa7dzs17rc30om9.png" alt="contosoOne-template" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since her team uses parameter files (one for each environment), she used the information from the &lt;a href="https://github.com/pazdedav/iac-meetup-arm/wiki/Templates-(definitions)" rel="noopener noreferrer"&gt;Wiki&lt;/a&gt; and correctly defined all required parameters.&lt;/p&gt;
&lt;h2&gt;
  
  
  Experimenting with Bicep
&lt;/h2&gt;

&lt;p&gt;As part of their continuous improvement principle, Mat's team was eager to learn more about &lt;strong&gt;project &lt;a href="https://github.com/azure/bicep" rel="noopener noreferrer"&gt;Bicep&lt;/a&gt;&lt;/strong&gt;, a new DSL language that could simplify authoring of ARM JSON templates.&lt;/p&gt;

&lt;p&gt;They started with creating a bicep &lt;a href="https://github.com/pazdedav/iac-meetup-arm/blob/master/src/bicep/contoso-storage.bicep" rel="noopener noreferrer"&gt;file&lt;/a&gt; that matches with the template spec for "contoso compliant storage account".&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pzk9rl31own1ae4nvv9.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pzk9rl31own1ae4nvv9.png" alt="bicep_storage_example" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All bicep files need to be "transpiled" to ARM JSON templates using &lt;code&gt;bicep build&lt;/code&gt; command. This requires Bicep CLI that can be downloaded from the project repo (releases).&lt;/p&gt;

&lt;p&gt;The team wanted to build a pipeline (a GitHub workflow) that would on "pull" take all bicep files in the repo (and a specific path, e.g. &lt;code&gt;src/bicep/*&lt;/code&gt;), transpile them to ARM JSON files, and deploy them as template specs to Azure to their 'Template Library'. &lt;/p&gt;

&lt;p&gt;At that time, the template specs required a special version of Resources PowerShell module (stored in a private repo, they had access to), so they tried to develop a &lt;strong&gt;custom &lt;a href="https://github.com/pazdedav/iac-meetup-arm/tree/master/.github/actions/deploy-template-specs" rel="noopener noreferrer"&gt;action&lt;/a&gt;&lt;/strong&gt; that would contain all required binaries (bicep CLI, Az.Resources module), perform all needed steps, and deploy to Azure using AZ CLI.&lt;/p&gt;

&lt;p&gt;They are still working on this improvement idea...&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: There has been a lot of development in both template specs and Bicep. There are several Actions in GitHub Marketplace (from the community) for working with Bicep files. Also, it is no longer required to have a special version of &lt;code&gt;Az.Resources&lt;/code&gt; PowerShell module to deploy template specs, and support for Azure CLI was also added.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Template Specs &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-specs?tabs=azure-powershell" rel="noopener noreferrer"&gt;concepts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Template Specs &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-specs-create-linked?tabs=azure-powershell" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Deployment &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/deploy-to-resource-group?tabs=azure-cli" rel="noopener noreferrer"&gt;scopes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Policy as Code with GitHub &lt;a href="https://docs.microsoft.com/en-us/azure/governance/policy/tutorials/policy-as-code-github?WT.mc_id=Portal-AzureTfsExtension#export-azure-policy-objects-from-the-azure-portal" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Project Bicep &lt;a href="https://github.com/azure/bicep" rel="noopener noreferrer"&gt;repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bicep &lt;a href="https://aka.ms/bicepdemo" rel="noopener noreferrer"&gt;Playground&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Meetup video on YouTube
&lt;/h3&gt;

&lt;p&gt;If you want to watch a video, where I presented this story together with several demos, you can watch it from the recording (my part begins at 8:31 and ends at 1:03:38): &lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/k0puV8XJ59E"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please note, this wasn't a conference-like session, but a local meetup, so the presentation is not very polished!! As a bonus, you can learn about Farmer from Evgeny Borzenin (@EvgenyBorzenin).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>bicep</category>
    </item>
    <item>
      <title>Using Azure Express Route for online data transfers</title>
      <dc:creator>David Pazdera</dc:creator>
      <pubDate>Sat, 07 Mar 2020 21:17:56 +0000</pubDate>
      <link>https://dev.to/pazdedav/using-azure-express-route-for-online-data-transfers-4i9e</link>
      <guid>https://dev.to/pazdedav/using-azure-express-route-for-online-data-transfers-4i9e</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Let's imagine this scenario: you have built a &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/hybrid-networking/expressroute" rel="noopener noreferrer"&gt;hybrid network&lt;/a&gt; between your corporate network and Azure using Azure Express Route and now you would like to leverage this private high-bandwidth connectivity for various purposes.&lt;/p&gt;

&lt;p&gt;First scenario that comes to my mind is &lt;strong&gt;data migration&lt;/strong&gt;, for example you want to move your 500TB archive to the cloud.&lt;/p&gt;

&lt;p&gt;You have read all about Azure Blob Storage and its &lt;a href="https://docs.microsoft.com/en-us/azure/storage/blobs/storage-lifecycle-management-concepts?tabs=azure-powershell" rel="noopener noreferrer"&gt;tiering model&lt;/a&gt;, and you think this can be the right solution.&lt;/p&gt;

&lt;p&gt;Since you now have both your "source" and "destination", the next logical step is to figure out &lt;strong&gt;"how"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On a high level, you have two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;online data transfer&lt;/li&gt;
&lt;li&gt;offline data transfer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;first category&lt;/strong&gt; is represented by tools (azcopy or Azure Storage SDK) and Azure services (like Azure Data Factory) and it requires a good network bandwidth (and time). The &lt;strong&gt;second category&lt;/strong&gt; is dominated by the Azure Data Box product &lt;a href="https://docs.microsoft.com/en-us/azure/databox-family/" rel="noopener noreferrer"&gt;family&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Transfer Advisor
&lt;/h2&gt;

&lt;p&gt;If you need some help with making the right choice, you could leverage quite &lt;strong&gt;new experience&lt;/strong&gt; in the Azure portal. When you provision a new Storage Account (or open an existing one), you can see the "Data Transfer" blade, where you fill in three attributes - estimated data size, available network bandwidth, and transfer frequency - and based on your input you will get a recommendation similar to this one:&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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fdata-transfer.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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fdata-transfer.png" alt="DataTransfer" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you feel confident that using the online method with &lt;code&gt;azcopy&lt;/code&gt; is the best option for you, especially since you have this nice 1Gbps private "pipe" to Azure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Little catch
&lt;/h2&gt;

&lt;p&gt;So far so good? Well, the moment you start planning your data transfer in details, you will (most likely) realize, that Azure Storage service exposes several &lt;strong&gt;public endpoints&lt;/strong&gt;, and for Blob storage it will look something like this: &lt;br&gt;
&lt;code&gt;https://cs44deib9068be17x4267xa4b.blob.core.windows.net/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Unless you configured something called &lt;strong&gt;Microsoft &lt;a href="https://docs.microsoft.com/en-us/azure/expressroute/expressroute-circuit-peerings#microsoftpeering" rel="noopener noreferrer"&gt;peering&lt;/a&gt;&lt;/strong&gt; on your Express Route circuit, all traffic from your network to those public endpoints will be routed over your Internet connection (router) and not via Express Route!&lt;/p&gt;

&lt;h2&gt;
  
  
  Private Link to the rescue
&lt;/h2&gt;

&lt;p&gt;Do not despair. Microsoft &lt;a href="https://azure.microsoft.com/en-gb/updates/private-link-now-available-in-ga/" rel="noopener noreferrer"&gt;launched&lt;/a&gt; a new service called &lt;strong&gt;Private Link&lt;/strong&gt; that &lt;a href="https://docs.microsoft.com/en-us/azure/private-link/private-link-overview" rel="noopener noreferrer"&gt;allows&lt;/a&gt; you to create a "private endpoint" that represents a specific instance of a PaaS service like Azure Storage and make it available (present it in form of a network interface card with a private IP) inside your Virtual Network.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F656ewg38uamw1yvbjip0.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F656ewg38uamw1yvbjip0.png" alt="PrivateLink" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This has several benefits like the ability to completely close public endpoints to your PaaS instance (block any access from the Internet) and make this instance available not only from within your VNet, but all peered VNets as well as your corporate network.&lt;/p&gt;

&lt;p&gt;How is this helping in our data transfer scenario? A lot. That Private Endpoint that I will attach to my target Blob storage will have a private IP address belonging to the IP range that is advertised via BGP protocol and my Express Route connection to my corporate network. In other words, if I use a tool like &lt;code&gt;azcopy&lt;/code&gt; or Azure Storage Explorer from a computer inside my network and target such private endpoint, this connection will &lt;strong&gt;utilize my Express Route connection&lt;/strong&gt; and its bandwidth. This is exactly what we wanted :)&lt;/p&gt;

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

&lt;p&gt;This is how our solution will look like in the end.&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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fdev-to-data_migraton_article.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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fdev-to-data_migraton_article.png" alt="SolutionDesign" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what we need to do, assuming that all networking bits and pieces (Virtual Network, Express Route circuit with Private peering, ER Gateway) have been provisioned and configured in advance:&lt;/p&gt;

&lt;p&gt;First, create a new Storage Account in the same region as your Virtual Network. In the 'Networking' part of the wizard, select &lt;strong&gt;Private endpoint&lt;/strong&gt; option:&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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard1.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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard1.png" alt="Wizard1" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Populate all important parameters of the new private endpoint and link it with your VNet:&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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard2.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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard2.png" alt="Wizard2" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can keep 'Integrate with Private DNS zone' option enabled, so the wizard also creates a private DNS zone (part of Azure DNS service offering). However, depending on your scenario, making this private zone available from your on-premises network requires additional configuration and careful planning. We will use a simpler option to make it working for our use case.&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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard3.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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard3.png" alt="Wizard3" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Depending what you provisioned in what Resource Group, the final result could look like this:&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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard4.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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fwizard4.png" alt="Wizard4" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  End-to-end testing
&lt;/h2&gt;

&lt;p&gt;There is one more step you need to do to complete the setup. As I mentioned above, the name resolution is a complex topic in &lt;strong&gt;hybrid DNS&lt;/strong&gt; scenarios, where you are in many cases bringing your existing DNS to Azure VNets while trying to utilize Azure DNS private zones. &lt;em&gt;I will leave this topic for another article :).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For now, we will use the &lt;a href="https://docs.microsoft.com/en-us/azure/private-link/private-endpoint-overview#dns-configuration" rel="noopener noreferrer"&gt;simplest option&lt;/a&gt; we have available, we will modify the &lt;code&gt;hosts&lt;/code&gt; file in our &lt;strong&gt;source server&lt;/strong&gt; (e.g. a machine, where we have our archive mounted) and add an entry that will resolve our blob endpoint to the private IP address of our Private endpoint. You can get both values from the Private endpoint resource:&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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fprivate-link.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%2Fcs44dfb9068ae17x4267xa4b.blob.core.windows.net%2Fpublic%2Fprivate-link.png" alt="PrivateEndpoint" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many &lt;a href="https://www.petri.com/easily-edit-hosts-file-windows-10" rel="noopener noreferrer"&gt;articles&lt;/a&gt; describing how to change hosts file, so I won't describe the process here.&lt;/p&gt;

&lt;p&gt;After adding that entry, you should be able to test it using &lt;code&gt;nslookup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What is left is testing the actual upload of the data. Follow this &lt;a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-blobs?toc=%2fazure%2fstorage%2fblobs%2ftoc.json" rel="noopener noreferrer"&gt;article&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authenticate to Azure using &lt;code&gt;AzCopy login&lt;/code&gt; command. Eventually you can use a SAS token (you need to create one in your &lt;strong&gt;target storage&lt;/strong&gt; account and append it to the target Uri).&lt;/li&gt;
&lt;li&gt;upload a directory with files (change both source drive/folder and destination Uri):
&lt;code&gt;azcopy copy 'C:\myDirectory' 
'https://mystorageaccount.blob.core.windows.net/mycontainer' --recursive&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This might look like a lot of work, but provisioning and configuring those handful of resources took me approx. 15 minutes (again assuming that all the "plumbing" was done beforehand), so it is definitely worth it.&lt;/p&gt;

</description>
      <category>azure</category>
    </item>
  </channel>
</rss>
