<?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: Adam the Automator</title>
    <description>The latest articles on DEV Community by Adam the Automator (@adbertram).</description>
    <link>https://dev.to/adbertram</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%2F86818%2Ff40f0b06-f847-4161-9ad2-cd873a57671c.jpg</url>
      <title>DEV Community: Adam the Automator</title>
      <link>https://dev.to/adbertram</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adbertram"/>
    <language>en</language>
    <item>
      <title>How to Set up the PAL Tool for Windows Performance Monitoring</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Fri, 15 Jan 2021 20:26:02 +0000</pubDate>
      <link>https://dev.to/adbertram/how-to-set-up-the-pal-tool-for-windows-performance-monitoring-4ki4</link>
      <guid>https://dev.to/adbertram/how-to-set-up-the-pal-tool-for-windows-performance-monitoring-4ki4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Jeff Stokes. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/jeff-stokes"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://adamtheautomator.com/tag/windows-10/"&gt;Windows&lt;/a&gt; performance counters can get unwieldy quickly. Although powerful, performance counters are notoriously complex. Even before you begin to create performance counters, it's sometimes impossible to know which ones to create to find what you're looking for in the first place! Thankfully, you have the PAL Tool.&lt;/p&gt;

&lt;p&gt;Introducing the open-source Performance Analysis of Logs (PAL) Tool. using the PAL tool, you can investigate performance problems and download performance counter logs.&lt;/p&gt;

&lt;p&gt;The PAL tool provides useful insights based on known thresholds. It works by providing a handy configuration GUI and then running a PowerShell script (&lt;em&gt;PAL.ps1&lt;/em&gt;) to parse and analyze performance counter log files.&lt;/p&gt;

&lt;p&gt;In this article, you're going to learn how to download the PAL tool, install it and then how to use it to perform system performance analysis of logs generated from data collector sets.&lt;/p&gt;

&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;If you'd like to follow along in this article, be sure you have the following prerequisites in place before starting.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any modern version of Windows - We'll be using Windows Server in this article.&lt;/li&gt;
&lt;li&gt;.NET Framework 4.7.2 installed&lt;/li&gt;
&lt;li&gt;An existing performance counter log - We'll be using the log created from a previous ATA article entitled &lt;em&gt;Windows Performance Monitoring: Saving Time with Templates&lt;/em&gt; &amp;lt;LINKHERE&amp;gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="downloading-and-installing-the-pal-tool"&gt;Downloading and Installing the PAL Tool&lt;/h2&gt;

&lt;p&gt;To get started, visit the  PAL Tool download from GitHub. You can do so by visiting the &lt;a href="https://github.com/clinthuffman/PAL/releases"&gt;releases section&lt;/a&gt;. In this article, we'll be using version 2.8.1.&lt;/p&gt;

&lt;p&gt;Find the MSI installer called &lt;em&gt;PAL_Setup_x_x_x.msi&lt;/em&gt; in the Assets section in the GitHub repo as shown below. Once downloaded, run the installer accepting all defaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mUWDyNxR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wcbda7tz3tt1b08js6nc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mUWDyNxR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wcbda7tz3tt1b08js6nc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="analyzing-counter-logs"&gt;Setting up the PAL Tool&lt;/h2&gt;

&lt;p&gt;Once the PAL tool is installed, it's time for the fun stuff! Let's now run through an example of using this handy tool to extract useful, applicable data from a performance counter log to check on server performance.&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;First, click the Start menu button, type &lt;em&gt;PAL&lt;/em&gt;, and click &lt;strong&gt;PAL&lt;/strong&gt; when it appears in the results. You'll see an example of what the &lt;strong&gt;PAL Wizard&lt;/strong&gt; looks like below. Click &lt;strong&gt;Next&lt;/strong&gt; to begin the &lt;strong&gt;PAL Wizard&lt;/strong&gt; and go to the &lt;strong&gt;Counter Log&lt;/strong&gt; tab.&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---i1nzMmb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jzrvflb3rma9v2uc8re3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---i1nzMmb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jzrvflb3rma9v2uc8re3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2.  On the &lt;strong&gt;Counter Log&lt;/strong&gt; tab is where you will tell PAL where the performance counter log is located and if you'd like to limit the analyzation to only a specific timeframe as as shown below.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;The optional &lt;strong&gt;Date/Time Range&lt;/strong&gt; is there only if you want to specify a time inside your BLG file. Leave it unchecked for now.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;3.  Click &lt;strong&gt;Browse&lt;/strong&gt;, navigate to the location of a BLG file you'd like to analyze, and open it. For this example, we have a Windows performance counter called &lt;em&gt;GIBSON_System_33920.blg&lt;/em&gt; that I created earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X25uVV-d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xite6lf8z8n4aewhjx0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X25uVV-d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xite6lf8z8n4aewhjx0s.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.  With the BLG file selected, click &lt;strong&gt;Next&lt;/strong&gt; to go to the &lt;strong&gt;Threshold File&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jo6G93wK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rirx16y6nnlpel4o82bx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jo6G93wK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rirx16y6nnlpel4o82bx.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many different options for Threshold Files. The default is &lt;strong&gt;System Overview&lt;/strong&gt;. Since this is just a demonstration without a specific metric to analyze in mind, keep the &lt;strong&gt;Title&lt;/strong&gt; set to &lt;strong&gt;System Overview&lt;/strong&gt; but feel free to play around with this.&lt;/p&gt;

&lt;p&gt;5.  Click &lt;strong&gt;Next&lt;/strong&gt; to go to the &lt;strong&gt;Questions&lt;/strong&gt; tab as you can see below. The &lt;strong&gt;Questions&lt;/strong&gt; tab is used for finding memory threshold limit breaches in x86 installs of Windows. It’s safe to skip this step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z3H8VlG8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qovyqnwsm5lzzm3hkssm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z3H8VlG8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qovyqnwsm5lzzm3hkssm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6.  Click &lt;strong&gt;Next&lt;/strong&gt; to go to the &lt;strong&gt;Options&lt;/strong&gt; tab as shown below. The &lt;strong&gt;Options&lt;/strong&gt; tab is used to break the log file into multiple files organized by chronological time. Usually the default, &lt;strong&gt;Auto&lt;/strong&gt;, is fine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YopwG0TD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/87bdp9i1gmq4pp4c0rdx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YopwG0TD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/87bdp9i1gmq4pp4c0rdx.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;7.  Click &lt;strong&gt;Next&lt;/strong&gt; to go to the &lt;strong&gt;Reports&lt;/strong&gt; tab as shown below. The &lt;strong&gt;Reports&lt;/strong&gt; tab is where you specify directories for report generation.&lt;/p&gt;

&lt;p&gt;By default, PAL creates a &lt;em&gt;PAL Reports&lt;/em&gt; folder within your &lt;em&gt;My Documents&lt;/em&gt; folder for Windows performance counter reports. This is useful if you tend to assist different groups/companies/teams and don’t want to mix the results data. As you can see below, an &lt;strong&gt;HTML Report&lt;/strong&gt; is going to be generated in the folder path provided.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_OBV_hk0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ttm9ed0tgr9r3ovmgldh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_OBV_hk0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ttm9ed0tgr9r3ovmgldh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;8.  Click &lt;strong&gt;Next&lt;/strong&gt; to go to the &lt;strong&gt;Queue&lt;/strong&gt; tab as shown below. The &lt;strong&gt;Queue&lt;/strong&gt; tab is where you can read what is going on under the hood with PAL. This will be a read-only field presenting the PowerShell script (&lt;em&gt;PAL.ps1&lt;/em&gt;) PAL is executing against the log file with a variety of variables defined.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;If you were analyzing several traces, the &lt;strong&gt;Queue&lt;/strong&gt; would reflect the individual commands being executed on each selected BLG file.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6xyr5QpM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rkql2opiecacjmw2btih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6xyr5QpM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rkql2opiecacjmw2btih.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;9.  Click &lt;strong&gt;Next&lt;/strong&gt; to go to the &lt;strong&gt;Execute&lt;/strong&gt; tab as shown below. The &lt;strong&gt;Execute&lt;/strong&gt; tab is the last step in the &lt;strong&gt;PAL Wizard&lt;/strong&gt;. Select &lt;strong&gt;Start analysis of the queue&lt;/strong&gt;, &lt;strong&gt;Execute as a low priority process&lt;/strong&gt;, and leave the &lt;strong&gt;Number of processing threads&lt;/strong&gt; to its default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wKyIpX_q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mqbxbhlh9ux3dlbziq5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wKyIpX_q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mqbxbhlh9ux3dlbziq5e.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;10.  Click &lt;strong&gt;Finish&lt;/strong&gt; to execute the PowerShell script.&lt;/p&gt;

&lt;p&gt;When configuration is completed, the PAL tool will execute a PowerShell script which opens a PowerShell window where you'll the see the script running. You can see an example of this below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--676yiVQi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s80dwnh7k86b82kt7y91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--676yiVQi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s80dwnh7k86b82kt7y91.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eventually the processing window will display that the script is generating the HTML report and graphs.&lt;/p&gt;

&lt;p&gt;When the script is completed, your default browser will open a new window with the html-based report as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NcTan7zY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ce24b82o8eknwsps76ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NcTan7zY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ce24b82o8eknwsps76ft.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The report is broken into 30 time slices, and also broken out by problem category (Disk, Memory, CPU, Network, etc). The report is interactive so you can click around the various links to navigate around and see what kind of useful information you can find.&lt;/p&gt;

&lt;p&gt;Most categories will have some friendly text at the category header explaining why the counter is relevant, what the thresholds are, and sometimes a MSDN or TechNet link for further reading. You can see below an example of what it says for &lt;strong&gt;Memory Available MBytes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8OWrpBjq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8h7x9ttdbzowtk7jnee6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8OWrpBjq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8h7x9ttdbzowtk7jnee6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;

&lt;p&gt;In this article, you learned about the handy PAL Tool. Using this free, open-source tool is a great way to perform analysis of logs, parse and extract actionable data from your performance monitor logs.&lt;/p&gt;

&lt;p&gt;Now that you're through this demonstration, start capturing other types of performance counter log and inspecting them with the PAL Tool to see what other benefits you can get from this useful tool!&lt;/p&gt;

</description>
      <category>windowsserver</category>
    </item>
    <item>
      <title>Saving Time with Windows PerfMon Templates</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Fri, 15 Jan 2021 20:20:48 +0000</pubDate>
      <link>https://dev.to/adbertram/saving-time-with-windows-perfmon-templates-3eoh</link>
      <guid>https://dev.to/adbertram/saving-time-with-windows-perfmon-templates-3eoh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Jeff Stokes. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/jeff-stokes"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do you rely on &lt;a href="https://techcommunity.microsoft.com/t5/Ask-The-Performance-Team/Windows-Performance-Monitor-Overview/ba-p/375481"&gt;Windows Performance Monitor&lt;/a&gt; to discover the root cause of Windows performance monitoring issues? Are you still struggling with creating lots of performance monitors at once or simply need a way to automate the process? If so, in this article, you're going to learn how to create perfmon templates and data collector set templates!&lt;/p&gt;

&lt;p&gt;In this article, you’ll learn how to do Windows Performance Monitoring by building XML performance monitoring templates to save yourself valuable time.&lt;/p&gt;

&lt;h2 id="how-templates-work-in-windows-performance-monitoring"&gt;How Templates Work in &lt;strong&gt;Windows Performance Monitoring&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Windows Performance Monitor (&lt;em&gt;perfmon&lt;/em&gt;) is a native &lt;a href="https://adamtheautomator.com/new-windows-terminal/"&gt;Windows&lt;/a&gt; tool for administrators to specify attributes like performance counters, event tracing for Windows events, and system configuration items. Once the attributes are set, &lt;em&gt;perfmon&lt;/em&gt; captures data into one of several formats: binary log, SQL, Data Source Name (DSN), or CSV.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;Only capture the data you need via performance monitoring. If not, you risk negatively affecting the performance of your servers!&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8xVKouWQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/orjtt37idkpsvbhd6xu1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8xVKouWQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/orjtt37idkpsvbhd6xu1.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfmon captures data from parameters set in data collector sets which control things like the log directory, log schedules, and any stop conditions. It is these data collector sets or groups of parameters that define what to log and when is how you can save time with templates.&lt;/p&gt;

&lt;p&gt;Templates, stored as XML, allow you to capture all of the parameters needed to query performance counters and to create data collections. Once you have that template, you can then easily transfer that data collector set to other machines.&lt;/p&gt;

&lt;p&gt;You can create perfmon templates from existing data collector sets and can be imported. These tasks are what you're primarily going to learn in this article.&lt;/p&gt;

&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;If you'd like to follow along with this tutorial, please be sure you have the following prerequisites in place.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows Server - We'll be using Windows Server 2019 in this article&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.7-zip.org/"&gt;7-zip&lt;/a&gt; to unzip some sample templates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="creating-performance-monitor-templates"&gt;Creating PerfMon Templates&lt;/h2&gt;

&lt;p&gt;Before you can leverage performance monitor templates for Windows performance monitoring, you must first create them. Creating a template consists of creating a data collector set as usual and then saving the settings as a template. The process is surprisingly simple.&lt;/p&gt;

&lt;p&gt;To create a perfmon template, on your Windows Server machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open up &lt;em&gt;Windows Performance Monitor (perfmon)&lt;/em&gt; via &lt;em&gt;perfmon.msc&lt;/em&gt; or by opening up via the start menu.&lt;/li&gt;
&lt;li&gt;In the left pane, expand &lt;strong&gt;Data Collector Sets&lt;/strong&gt; and click on &lt;strong&gt;User Defined&lt;/strong&gt;. This allows you to customize the kind of data you'll be collecting.&lt;/li&gt;
&lt;li&gt;Right-click on &lt;strong&gt;User Defined&lt;/strong&gt; and click &lt;strong&gt;New&lt;/strong&gt; —&amp;gt; &lt;strong&gt;Data Collector Set&lt;/strong&gt; to open the &lt;strong&gt;Create New Data Collector Set&lt;/strong&gt; window as shown below. Type a &lt;strong&gt;Name&lt;/strong&gt; for your data collector set, click &lt;strong&gt;Create manually (Advanced)&lt;/strong&gt;, and click &lt;strong&gt;Next&lt;/strong&gt;. Note the &lt;strong&gt;Create from a template (Recommended)&lt;/strong&gt; option. This is the same place you'll go when importing a template later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2IVz77cu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d5as0fpzwwpijq3vhtit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2IVz77cu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d5as0fpzwwpijq3vhtit.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.  In the next screen, you'll be prompted to select what kind of data you'd like to collect as shown below. These options are up to you. Once you do this, click on &lt;strong&gt;Finish&lt;/strong&gt; and your data collector set will be stored directly under &lt;strong&gt;User Defined&lt;/strong&gt; as you'll see in the upcoming screenshot.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;Depending on which boxes you clicked, you will be prompted to specify the parameters for Performance counter, Event trace data, and System configuration information. That is a deep dive for another tutorial.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T3qhhQ2Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8qdn0qos77dacunrut59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T3qhhQ2Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8qdn0qos77dacunrut59.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5.  As shown below, right-click on the data collector set just created and click &lt;strong&gt;Save Template&lt;/strong&gt;. You will then be prompted where to save the XML file to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mAnC_JN4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ke8pojr59xh6jveyn7ag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mAnC_JN4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ke8pojr59xh6jveyn7ag.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! That's all there is to creating a performance monitoring template. At this point, you can follow the same steps you'll be learning about in the next section to create a data collector set from this template.&lt;/p&gt;

&lt;h2 id="creating-a-data-collector-set-from-a-template"&gt;Creating a Perfmon Data Collector Set Template&lt;/h2&gt;

&lt;p&gt;Sometimes it's just faster to start with someone else's vast and learned accomplishments when it comes to Windows performance monitoring. Smugness aside, let's download and use a pre-existing template. The template you'll be using is a base operating system capture. It does not include any performance counter references to applications like Microsoft Exchange or SQL, for example.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;When creating a data collector set from template, be sure the computer the data collector set is being created on has the same performance counters as the source computer.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;To import the example XML Template into &lt;em&gt;perfmon&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://github.com/jeffstokes72/Perf-Templates"&gt;GitHub repository&lt;/a&gt;, download the &lt;em&gt;performance_capture - rolling 200.7z&lt;/em&gt; file and extract it's contents.&lt;/li&gt;
&lt;li&gt;On Windows Server, open up &lt;em&gt;perfmon&lt;/em&gt;, right-click on &lt;strong&gt;User Defined&lt;/strong&gt; and click &lt;strong&gt;New&lt;/strong&gt; —&amp;gt; &lt;strong&gt;Data Collector Set&lt;/strong&gt; to open the &lt;strong&gt;Create New Data Collector Set&lt;/strong&gt; window as shown below. Type the &lt;strong&gt;Name&lt;/strong&gt; for the data collector set and ensure &lt;strong&gt;Create from a template (Recommended)&lt;/strong&gt; is selected and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DWq_yhkI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vyo253t0dfc15645nuui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DWq_yhkI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vyo253t0dfc15645nuui.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.  The template data collector set in this template example is a Basic one. Choose &lt;strong&gt;Basic&lt;/strong&gt; and click &lt;strong&gt;Next&lt;/strong&gt;. Then click &lt;strong&gt;Browse&lt;/strong&gt; to open the browse window and pick the &lt;em&gt;performance_capture - rolling 200.xml&lt;/em&gt; file extracted from the ZIP file earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ZY9Y0wW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hy5wdyotyn6502ixi02a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ZY9Y0wW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hy5wdyotyn6502ixi02a.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the XML template has been selected, perfmon will read the template and provide the template data collector set it found along with any notes in the template as shown below. Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N_Usx1T9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lcbky8esu5z1rjvdbkdf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N_Usx1T9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lcbky8esu5z1rjvdbkdf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.  Select a place to save the logs generated from this data collector set. You can see below the default path has been selected for you. This path is in the XML template. Leave the default file location as-is and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R0HJCCE7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/btjuv4bxtai1fjjhr390.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R0HJCCE7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/btjuv4bxtai1fjjhr390.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5.  Ensure &lt;strong&gt;Save and close&lt;/strong&gt; is selected and click &lt;strong&gt;Finish&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HMcLGuYn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ogsbcafufz11fuj8nmrl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HMcLGuYn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ogsbcafufz11fuj8nmrl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You have now created a data collector set from an XML template.&lt;/p&gt;

&lt;h2 id="starting-the-data-collector-set"&gt;Starting the Data Collector Set&lt;/h2&gt;

&lt;p&gt;The data collector set is now built from the XML template but it's not doing much at the moment. You have to start the data collector set. To do so:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;em&gt;perfmon&lt;/em&gt; if it's not already open.&lt;/li&gt;
&lt;li&gt;In the left pane, expand &lt;strong&gt;Data Collector Sets&lt;/strong&gt; —&amp;gt; &lt;strong&gt;User Defined&lt;/strong&gt;. You should now see the data collector set you just created from the template.&lt;/li&gt;
&lt;li&gt;Right-click your custom data collector set and click &lt;strong&gt;Start&lt;/strong&gt; from the pop-up menu as shown below. Performance counters will now begin collecting data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pAOCP_XB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tw742abb88o71s6zonmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pAOCP_XB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tw742abb88o71s6zonmd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="reviewing-the-created-data-collector-set"&gt;Reviewing the Created Data Collector Set&lt;/h2&gt;

&lt;p&gt;Once the data collector set has been created via XML template, you can now inspect the settings to ensure &lt;em&gt;perfmon&lt;/em&gt; imported all expected settings.&lt;/p&gt;

&lt;p&gt;From &lt;em&gt;permon&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Expand &lt;strong&gt;Data Collector Sets&lt;/strong&gt; —&amp;gt; &lt;strong&gt;User Defined&lt;/strong&gt; in the left pane.&lt;/li&gt;
&lt;li&gt;Right-click the custom data collector set just created and click &lt;strong&gt;Properties&lt;/strong&gt; from the pop-up menu to open the &lt;strong&gt;Properties&lt;/strong&gt; dialog box as you can see below.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zdnWdWuu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/m7cpp65y4hklud25lg37.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zdnWdWuu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/m7cpp65y4hklud25lg37.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.  Click the Schedule tab. In this tab, verify the &lt;strong&gt;Start&lt;/strong&gt; date is in the past and the checkbox for &lt;strong&gt;All schedules enabled&lt;/strong&gt; is checked. This will make sure your server will resume logging data in the event of a reboot/crash.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9TeXwkKD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h7dpx89yih69wi6k4is7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9TeXwkKD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h7dpx89yih69wi6k4is7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.  Click the Stop Condition tab. Here, verify the &lt;strong&gt;Maximum Size&lt;/strong&gt; of the output file is 200MB as well as the &lt;strong&gt;Maximum Size&lt;/strong&gt; checkbox is checked. Anything larger than 200MB becomes unwieldy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_LvBxACS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s6op0vof6gj5btfnfq9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_LvBxACS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s6op0vof6gj5btfnfq9v.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5.  Click OK to close the &lt;strong&gt;Properties&lt;/strong&gt; dialog box.&lt;/p&gt;

&lt;h2 id="reviewing-the-data-collector"&gt;Reviewing the Data Collector&lt;/h2&gt;

&lt;p&gt;The XML template creates a data collector set but inside of that set are individual data collectors. You should also inspect the data collector created by the template. To do that:&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Select the data collector in the middle pane, right-click, and click &lt;strong&gt;Properties&lt;/strong&gt; from the pop-up menu as shown below.&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fs5g8S0v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ew7gasp8lzrdlbjgos8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fs5g8S0v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ew7gasp8lzrdlbjgos8g.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2.  In the &lt;strong&gt;Performance Counter Properties&lt;/strong&gt; dialog box, ensure the &lt;strong&gt;Performance Counters&lt;/strong&gt; tab is selected as shown below.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;For this exercise, we are just taking a peek. Note the Performance counters being collected. In the future you can add or remove them to suit your needs. Also note the Log format is binary (BLG). Unlike tab or comma-separated value file output, BLG has a dynamic schema. This is helpful if a process starts after the log starts, it will still be included in the log file. Also notice the sample rate on this tab.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sN1FRF8w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/z67vt0eh4cnupr57opd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sN1FRF8w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/z67vt0eh4cnupr57opd3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.  Click the &lt;strong&gt;File&lt;/strong&gt; tab. In this box, the &lt;strong&gt;Log mode&lt;/strong&gt; should be Circular. This mode captures the last several hours of the systems' activity and is written in a first-in, first-out method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R5ZoFtiF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xpmh6dlfnn77x462l0fs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R5ZoFtiF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xpmh6dlfnn77x462l0fs.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.  Click &lt;strong&gt;OK&lt;/strong&gt; to close the Properties dialog box and you're done!&lt;/p&gt;

&lt;h2 id="wrapping-it-up"&gt;Wrapping up Perfmon Templates&lt;/h2&gt;

&lt;p&gt;Using performance monitoring templates to create data collector sets is a convenient way to manage performance counters at scale or via automation. You've now seen an example of using templates. Armed with that knowledge, how do you plan to use templates in your day-to-day tasks?&lt;/p&gt;

</description>
      <category>windows</category>
    </item>
    <item>
      <title>How to Enable and Configure Azure JIT for VMs</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Fri, 15 Jan 2021 20:10:48 +0000</pubDate>
      <link>https://dev.to/adbertram/how-to-enable-and-configure-azure-jit-for-vms-4a26</link>
      <guid>https://dev.to/adbertram/how-to-enable-and-configure-azure-jit-for-vms-4a26</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Jeff Christman. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/jeff"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hackers are continually scanning and actively hunting for accessible virtual machines with open management ports. If you're VMs are in Azure though, you have a tool at your disposal called &lt;a href="https://docs.microsoft.com/en-us/azure/security-center/security-center-just-in-time?tabs=jit-config-asc%2Cjit-request-asc"&gt;Azure Just-in-Time Access (JIT)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Azure JIT is a service that allows you to open network ports "just in time" or only when you need them open and no longer. Part of the &lt;a href="https://azure.microsoft.com/en-us/services/security-center/#features"&gt;Azure Security Center&lt;/a&gt; suite of products, Azure JIT is a component inside of the broader &lt;a href="https://azure.microsoft.com/en-us/services/azure-defender/"&gt;Azure Defender brand&lt;/a&gt;. With Azure Defender and JIT, you can control who has access to network ports on any of your Azure virtual machines.&lt;/p&gt;

&lt;p&gt;In this article, you learn what Azure Just-In-Time access is, how to enable it, configure options and policies, and how to request access. We will also walk through different ways to allow JIT access using the Azure portal, PowerShell, and the &lt;a href="https://docs.microsoft.com/en-us/rest/api/securitycenter/"&gt;Azure Security Center API.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once enabled, you'll then learn how request access to VMs and also how to audit Azure JIT activity.&lt;/p&gt;

&lt;h2 id="h-prerequisites"&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;This will be a hands-on tutorial blog post. If you would like to follow along, be sure to have the following prerequisites in place before starting this article. You'll need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;a href="https://azure.microsoft.com/en-us/free/"&gt;Azure Subscription&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Logged into the &lt;a href="https://portal.azure.com/"&gt;Azure Portal&lt;/a&gt; with an Azure account with the &lt;em&gt;Subscription Owner&lt;/em&gt; role. For more detail, check out the &lt;a href="https://docs.microsoft.com/en-us/azure/security-center/security-center-permissions"&gt;&lt;em&gt;Permissions in Azure Security Center documentation&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;Standard&lt;/em&gt; Azure Defender plan. You can sign up while logged into the Azure Portal via &lt;a href="https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/18"&gt;Azure Security Center&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shell.azure.com"&gt;Azure Cloud Shell&lt;/a&gt; or PowerShell. Be sure you log in once to create the storage account it needs at least once. Later on in the article, you'll learn how to enable Azure JIT using PowerShell. Azure Cloud Shell is the easiest way to do that unless you'd like to use the &lt;a href="https://adamtheautomator.com/connect-azaccount-powershell/"&gt;Azure PowerShell module&lt;/a&gt; installed on your local computer.&lt;/li&gt;
&lt;li&gt;The Azure Defender service enabled. Part of Azure Security Center, you'll need to first &lt;a href="https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/18"&gt;enable it&lt;/a&gt; on your subscription.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;If you'd like to manage on-prem Windows or Linux servers, be sure to install the &lt;a href="https://docs.microsoft.com/en-us/azure/security-center/defender-for-servers-introduction"&gt;Azure Defender for Servers&lt;/a&gt; software.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id="h-azure-jit-pricing"&gt;Azure JIT Pricing&lt;/h2&gt;

&lt;p&gt;Before you get too far, know that Azure JIT is not free. There are various Azure JIT pricing levels you need to know before diving in headfirst.&lt;/p&gt;

&lt;p&gt;You can find all of the pricing options available on the &lt;a href="https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/18"&gt;Azure Security Center blade in the Azure portal&lt;/a&gt;. Since Azure JIT is part of Azure Defender (which is part of Azure Security Center), the pricing you're looking for is for Azure Defender.&lt;/p&gt;

&lt;p&gt;Once you've enabled Azure Defender, you'll notice a &lt;strong&gt;Pricing and Settings&lt;/strong&gt; option on the left-hand side of the page as shown below in the Azure portal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EQpqAdJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8qprny8t5ibpk22zhpna.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EQpqAdJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8qprny8t5ibpk22zhpna.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Azure Defender is free for the first 30 days and anything beyond the initial 30 days will be charged as per the pricing chart below. The chart below shows the per month pricing for the various services available that can utilize Azure Defender. Generally speaking, you can expect a cost of about $15.00 per month for your virtual machines the use Azure Defender and Just-In-Time service.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;All prices are valid as of 10/06/20. You can find the latest prices on the &lt;a href="https://azure.microsoft.com/en-au/pricing/details/security-center/"&gt;Azure Security Center pricing page&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="h-azure-defender-pricing"&gt;Azure Defender Pricing&lt;/h2&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Resource&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for Servers&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$14.60/Server/Month&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for App Service&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$14.60/App Service/Month&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for SQL&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$15/Server/Month&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for MySQL&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for PostgreSQL&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for Storage&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$0.02/10K Transcations&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for IoT - By Device&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$0.001/month&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for IoT - By Messages&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$0.20/25K Transactions&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for Kubernetes&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$0.00268/vCore/hour&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for ACR&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$0.29/image&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;tr&gt;
&lt;br&gt;
&lt;td&gt;Azure Defender for Key Vault&lt;/td&gt;
&lt;br&gt;
&lt;td&gt;$0.01/10K Transactions&lt;/td&gt;
&lt;br&gt;
&lt;/tr&gt;
&lt;br&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
Azure Defender Pricing




&lt;h2 id="h-enabling-azure-jit-on-azure-vms"&gt;Enabling Azure JIT on Azure VMs&lt;/h2&gt;

&lt;p&gt;The first task you'll need to accomplish is enabling the Azure JIT feature on each of your VMs. This can be done through the &lt;a href="https://portal.azure.com/#blade/Microsoft_Azure_Security/SecurityMenuBlade/18"&gt;Azure Security Center in the Azure portal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a simple demonstration, this section will show you how to enable JIT on a single VM.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;If you have hundreds of VMs, don't worry. You'll learn how to enable JIT in bulk with PowerShell a little later.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;ol&gt;&lt;li&gt;Once at the Azure Security Center page, click on Azure Defender on the left side of the page. You should then see a Just-in-time VM access title as shown below.&lt;/li&gt;&lt;/ol&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kSnt1Jry--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gnjv7va27ep6qvywp31k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kSnt1Jry--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gnjv7va27ep6qvywp31k.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2. Next, click on the Just-in-time VM access tile. This will open the JIT settings blade. Once open, the blade will show three tabs: Configured, Not Configured, and Unsupported. These tabs represent the various state of Azure JIT against each VM in your subscription.&lt;/p&gt;

&lt;p&gt;3. If this is your first time enabling Azure JIT, all of your VMs will be under the Not Configured tab as shown below. To enable JIT on the VM, click on the VM and then click on the Enable JIT on 1 VM button.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;Note that the &lt;strong&gt;Resource Group&lt;/strong&gt; and &lt;strong&gt;Subscription Name&lt;/strong&gt; will be unique to your environment.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cgz_z1n0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vtaldn7syojcalx86x2o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cgz_z1n0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vtaldn7syojcalx86x2o.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4. Once you click on the Enable JIT button, you'll then see a JIT VM access configuration page. This page allows you to view all of the default ports and also add any existing ports you'd like to actively manage.&lt;/p&gt;

&lt;p&gt;To add a new port, click on &lt;strong&gt;Add&lt;/strong&gt; and provide the &lt;strong&gt;Port&lt;/strong&gt;, &lt;strong&gt;Protocol&lt;/strong&gt;, &lt;strong&gt;Allowed source IPs,&lt;/strong&gt; and &lt;strong&gt;IP addresses&lt;/strong&gt; fields as required. When complete, click on &lt;strong&gt;OK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1ikJdMi_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lesky0xc8zf8l2knq789.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1ikJdMi_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lesky0xc8zf8l2knq789.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When complete, you'll then have ensured the ports under management are only open for a specific amount of time (&lt;strong&gt;Max request time&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Once you set an access policy, when a user requests access (shown how below), a firewall rule is created with the settings set in the policy. After the &lt;strong&gt;Max request time&lt;/strong&gt; has been exceeded, Azure will remove the firewall rule that opened the port, to begin with.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;Know that &lt;/em&gt;&lt;a href="https://azure.microsoft.com/en-us/services/azure-bastion/#:~:text=Azure%20Bastion%20is%20a%20new,the%20Azure%20portal%20over%20SSL." rel="noreferrer noopener"&gt;Azure Bastion&lt;/a&gt;&lt;em&gt; and JIT access cannot be used together. If you enable Azure Bastion with an existing JIT access policy enabled on a VM, the bastion host will not connect to the target machine and you will get a connection error!&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="h-requesting-access-to-a-virtual-machine"&gt;Requesting Access to a Virtual Machine&lt;/h2&gt;

&lt;p&gt;Now that you've enabled JIT on a VM, how do you (or your users) get access to that VM through one of the managed ports?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;While logged into the Azure portal, navigate to &lt;a href="https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.Compute%2FVirtualMachines"&gt;the VM&lt;/a&gt; you'd like to access and select &lt;strong&gt;Connect&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Now, select the protocol you'd like to use and change the &lt;strong&gt;Port number&lt;/strong&gt; or desired &lt;strong&gt;Source IP&lt;/strong&gt;, if required. Typically, when you attempt to connect to a VM, you'll immediately be able to download an RDP file or see SSH methods, for example. When a JIT access policy is applied to a VM, you'll see options like in the below screenshot. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfpMeh----/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sgcifhk2syt7rteqqwgv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfpMeh----/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sgcifhk2syt7rteqqwgv.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;Know that requesting access to VMs only opens the requested port. Just like logging into a virtual machine using RDP, you will need a valid Active Directory account with the appropriate permissions to log in to the virtual machine.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="h-automating-azure-jit-using-powershell"&gt;Automating Azure JIT using PowerShell&lt;/h3&gt;

&lt;p&gt;So let's say you've got hundreds of VMs that you need to control management ports on. Maybe you've got a request from management to get all of VM management ports locked own ASAP. You're not going to want to go through the steps you just took to enable an access policy on a single VM. Let's automate this.&lt;/p&gt;

&lt;p&gt;Instead of logging into the Azure Security Center each time and enabling a JIT access policy for each VM, you can write a script using PowerShell. Once you've built the script, you can then incorporate it into a CI/CD pipeline too.&lt;/p&gt;

&lt;p&gt;Below you'll find a copy/pastable code snippet for you to get started. This code creates and applies a JIT access policy to the &lt;em&gt;VMName&lt;/em&gt; VM that limits Port 22 (SSH) and RDP (3389), all protocols, from all source IPs for a maximum of three hours.&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-powershell"&gt;## Build the policy with the VM's resource ID
$JitPolicy = (@{ id="/subscriptions/SUBSCRIPTIONID/resourceGroups/RESOURCEGROUP/providers/Microsoft.Compute/virtualMachines/VMNAME"
ports=(@{
     number=22; ## SSH
     protocol="*"; ## all protocols
     allowedSourceAddressPrefix=@("*"); ## any source IP
     maxRequestAccessDuration="PT3H"}, ## up to three hours
     @{
     number=3389;
     protocol="*";
     allowedSourceAddressPrefix=@("*");
     maxRequestAccessDuration="PT3H"})})
$JitPolicyArr=@($JitPolicy)

## Send the policy to Azure and commit it to the VM
Set-AzJitNetworkAccessPolicy -Kind "Basic" -Location "LOCATION" -Name VMNAME -ResourceGroupName "RESOURCEGROUP" -VirtualMachine $JitPolicyArr&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="h-auditing-jit-access-activity"&gt;Auditing JIT Access Activity&lt;/h2&gt;

&lt;p&gt;So you've set up JIT access to a bunch of VMs but is it being used? If so, how often? You need to audit the access policy activity. Auditing can be done via each VM's activity log.&lt;/p&gt;

&lt;p&gt;When someone requests access to a management port and Azure creates an access policy, JIT logs this activity in the activity log. The activity log contains information such as when the request was made, who made the request, if it was successful, and so on.&lt;/p&gt;

&lt;p&gt;To view the activity log, navigate to the JIT VM Access blade in the Azure Portal, click on the three ellipses on the far right of the selected virtual machine, and select &lt;strong&gt;Activity Log&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9GwTJP-l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xblvq2uc4x9cjry57w02.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9GwTJP-l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xblvq2uc4x9cjry57w02.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see below, the activity log tracks when an access polity has been applied to a virtual machine, who requested access, and when access was granted or denied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nQrjDzGV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4frhqe708l1khgmh9rgl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nQrjDzGV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4frhqe708l1khgmh9rgl.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="h-further-reading"&gt;Further Reading&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/security-center/just-in-time-explained"&gt;&lt;strong&gt;&lt;em&gt;Understanding Just-In-Time VM Access&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/security-center/security-center-just-in-time?tabs=jit-config-asc%2Cjit-request-asc"&gt;&lt;strong&gt;&lt;em&gt;Secure your management ports with Just-In-Time access&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/learn/modules/secure-vms-with-azure-security-center/3-exercise-jit-vm-access"&gt;Microsoft Learn - Exercise - Enable JIT VM Access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/en-au/pricing/details/security-center/"&gt;Azure Just-in-Time Pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>virtualmachines</category>
      <category>azurejit</category>
    </item>
    <item>
      <title>How To Make Visual Studio Code Look And Behave Like The PowerShell ISE</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 23:12:40 +0000</pubDate>
      <link>https://dev.to/adbertram/how-to-make-visual-studio-code-look-and-behave-like-the-powershell-ise-2cgg</link>
      <guid>https://dev.to/adbertram/how-to-make-visual-studio-code-look-and-behave-like-the-powershell-ise-2cgg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Jeff Christman. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/jeff"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Despite its lack of features and options, the PowerShell ISE used to be the primary PowerShell IDE tool to develop and edit &lt;a href="https://adamtheautomator.com/tag/powershell/"&gt;PowerShell&lt;/a&gt; Scripts. It offered an integrated development environment (IDE) that included some basic features to build scripts and modules.&lt;/p&gt;

&lt;p&gt;Microsoft is no longer actively developing the PowerShell Integrated Scripting Environment (ISE) and is being replaced by the more powerful and versatile open source Visual Studio Code (VS Code). With its ever-expanding options and extensions, VS Code is quickly becoming the new standard tool for developing not only PowerShell, but just about any other language you choose.&lt;/p&gt;

&lt;p&gt;Despite all the new features available in VS Code, leaving the familiar environment of the ISE is difficult. It is like watching your child go to college. You are proud of the achievement but sad about having left a comfortable environment.&lt;/p&gt;

&lt;p&gt;VS Code can be intimidating at first. As the default settings of VS Code can be a little hard to work with if you are used to working with the ISE. However, it's highly customizable, and with the addition of Extensions and a few configuration settings, you can make VS Code look and behave just like PowerShell ISE.&lt;/p&gt;

&lt;p&gt;If you're more of a visual learner, be sure to check out this posts associated TechSnips how-to video.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=Ar6YSFC0xBE"&gt;https://www.youtube.com/watch?v=Ar6YSFC0xBE&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="the-look"&gt;The Look&lt;/h2&gt;

&lt;p&gt;To get VS Code to look like PowerShell ISE, the PowerShell Extension needs to be installed. To install, select the setting gear at the bottom left, then pick &lt;em&gt;Extensions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;At the search box, type in &lt;em&gt;Powershell&lt;/em&gt; and then install. This extension adds a few features to the default settings of VS Code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9PUtqG1R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a5lj03lrefh76mujnval.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9PUtqG1R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a5lj03lrefh76mujnval.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tay1ijsA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2pa7zrkk6c2f3j071pkq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tay1ijsA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2pa7zrkk6c2f3j071pkq.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get the distinctive look of PowerShell ISE, select the settings gear and then &lt;em&gt;Color Theme&lt;/em&gt;. Choose the&lt;em&gt; PowerShell ISE&lt;/em&gt; theme.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vBT_7KhJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bybuospp8a5qkk4p5khg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vBT_7KhJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bybuospp8a5qkk4p5khg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that you have the look of PowerShell ISE, we need to set the behavior to match ISE.&lt;/p&gt;

&lt;h2 id="the-behavior"&gt;The Behavior&lt;/h2&gt;

&lt;p&gt;The default install of VS Code lacks some features of the PowerShell ISE, such as Zoom, Tab-Completion, Intellisense, and Code Snippets.&lt;/p&gt;

&lt;p&gt;For setting the environment to match that of PowerShell ISE, we need to add some environment settings to the VS Code settings.&lt;/p&gt;

&lt;h3 id="keyboard-and-mouse-actions"&gt;Keyboard and Mouse Actions&lt;/h3&gt;

&lt;p&gt;Open the command palette using the &lt;em&gt;ctrl+Shift+P&lt;/em&gt; key combination. In the command palette box, enter &lt;em&gt;Preferences Open Settings (JSON)&lt;/em&gt;. This will open up a two-pane window with the user settings on the right. Insert the following code between the brackets on the right pane.&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-json"&gt;"editor.mouseWheelZoom": true,
"editor.minimap.enabled": false,
"editor.renderControlCharacters": true,
"editor.wordWrap": "on",
"files.trimTrailingWhitespace": true,
"files.autoSave": "afterDelay",
"powershell.integratedConsole.showOnStartup": false,
"terminal.integrated.fontFamily": "Consolas",
"terminal.integrated.fontSize": 18,
"terminal.integrated.shell.windows": "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe", "powershell.powerShellExePath": "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nDe7mQ8t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gd92jd7k2ys5it59qme3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nDe7mQ8t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gd92jd7k2ys5it59qme3.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="code-snippets"&gt;Code snippets&lt;/h2&gt;

&lt;p&gt;One of the best features of PowerShell ISE is the ability to use Code Snippets. VS Code has made code snippets more versatile.&lt;/p&gt;

&lt;p&gt;To add code snippets, select the setting gear and then pick &lt;em&gt;User Snippets&lt;/em&gt;. In the command palette, enter &lt;em&gt;powershell.json&lt;/em&gt;. I've created a sample user snippets JSON file for you available &lt;a href="https://github.com/TechSnips/SnipScripts/blob/master/How%20To%20Make%20Visual%20Studio%20Code%20Look%20And%20Behave%20Like%20The%20PowerShell%20ISE/PowerShel.json"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;

&lt;p&gt;VS Code is now the preferred PowerShell editor. With a few customizations, we can make it behave just like the familiar PowerShell ISE.&lt;/p&gt;

</description>
      <category>powershell</category>
    </item>
    <item>
      <title>Understanding LDIF (LDAP Data Interchange Format)</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 23:06:45 +0000</pubDate>
      <link>https://dev.to/adbertram/understanding-ldif-ldap-data-interchange-format-40je</link>
      <guid>https://dev.to/adbertram/understanding-ldif-ldap-data-interchange-format-40je</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Stuart Squibb. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/stuart"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've worked with LDAP (Lightweight Directory Access Protocol) before, you've probably run across LDIF files. LDIF is a schema defined in text files that defines LDAP directory content as a set of records. LDIF files define one record for each object (or entry) and store actions to perform on those objects like adding, modifying, removing and renaming objects.&lt;/p&gt;

&lt;p&gt;In this article, you'll get a deeper understanding of what the LDIF format is, what a file looks like and how it's used by various tools.&lt;/p&gt;

&lt;h2 id="the-ldif-format"&gt;The LDIF Format&lt;/h2&gt;

&lt;p&gt;The LDIF format is always represented in plain-text files with an LDF extension. Because they are plain-text,  you can easily view them in your favorite text editor. &lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt;, for example, has a &lt;a href="https://marketplace.visualstudio.com/items?itemName=jtavin.ldif"&gt;syntax highlighter extension for LDIF files&lt;/a&gt; that you can install which will make working with LDIF files easier.&lt;/p&gt;

&lt;p&gt;An LDIF file consists of a number of &lt;em&gt;records&lt;/em&gt; each separated from the next by a blank line. Each record refers to a single &lt;em&gt;object&lt;/em&gt; and consists of a number of lines. You can see an example of the structure below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6mZ1KnC8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9zu6576n3ig2bathvlwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6mZ1KnC8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9zu6576n3ig2bathvlwp.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each line of a record can contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An LDAP attribute&lt;/strong&gt; - This is a line that consists of an LDAP attribute name, followed by a colon, a space and then the attribute value.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;em&gt;&lt;strong&gt;A &lt;em&gt;changetype&lt;/em&gt;&lt;/strong&gt;&lt;/em&gt; -&lt;/em&gt; A changetype line in a record identifies the type of change to be made to the AD object (add, delete, modify, moddn/modrdn). A changetype of modify is followed by the change &lt;em&gt;operations&lt;/em&gt; themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A change operation&lt;/strong&gt; - These lines represent the changes to be made (add, delete or replace). If object is being changed more than one way, you may also notice a dash representing separating multiple change operations within a &lt;em&gt;modify&lt;/em&gt; &lt;em&gt;changetype&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A comment line&lt;/strong&gt; - These are lines that start with the hash (“#”) character.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each record correlates to an AD object. Each AD object that the record refers to is identified by the first line of the record, which contains the distinguished name (DN) attribute for the LDAP object.&lt;/p&gt;

&lt;h2 id="example-ldf-file"&gt;Example LDF File&lt;/h2&gt;

&lt;p&gt;In the next screenshot, you will see a snippet of an LDIF file generated for a user LDAP object type. This file was exported with the &lt;a href="https://adamtheautomator.com/csvde-ldifde"&gt;LDIFDE tool&lt;/a&gt; representing a user account for Paul Cox.&lt;/p&gt;

&lt;p&gt;Each line of the example file below represents one of the attributes for Paul, apart from the comment line.&lt;/p&gt;

&lt;p&gt;You'll see that this record has two multi-valued LDAP/Active Directory attributes such as &lt;code&gt;objectClass&lt;/code&gt; and &lt;code&gt;memberOf&lt;/code&gt;. These attributes are represented by multiple lines - one for each value. Also note that the &lt;code&gt;objectGUID&lt;/code&gt; attribute is &lt;a href="https://en.wikipedia.org/wiki/Base64"&gt;base64&lt;/a&gt; encoded. Attributes are encoded in the LDIF file because that particular attribute is not stored in LDAP as a plain text or numeric value. Instead, it is stored as a 16-byte binary value.&lt;/p&gt;

&lt;p&gt;Since this example file was exported with the ldifde tool, it is ready to be re-imported elsewhere so it has a &lt;em&gt;changetype&lt;/em&gt; of &lt;em&gt;add&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7uxRowdn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gsupumwgkr82s6bclgdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7uxRowdn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gsupumwgkr82s6bclgdv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="ldif-support-in-tools"&gt;LDIF Support in Tools&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, one tool you can work with LDIF files is called &lt;a href="https://adamtheautomator.com/csvde-ldifde"&gt;ldifde in Windows&lt;/a&gt; but others exist as well. LDIF files can be generated and consumed by a variety of tools such the &lt;a href="https://www.openldap.org/"&gt;OpenLDAP&lt;/a&gt; tools available on Linux and Mac OS, the python module &lt;a href="https://www.python-ldap.org/en/latest/reference/ldif.html"&gt;python-ldap&lt;/a&gt; and the perl &lt;a href="https://metacpan.org/pod/Net::LDAP::LDIF"&gt;Net::LDAP::LDIF&lt;/a&gt; module.&lt;/p&gt;

&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://tools.ietf.org/html/rfc2849"&gt;&lt;strong&gt;&lt;em&gt;RFC 2849 - The LDAP Data Interchange Format technical specification&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;

</description>
      <category>ldif</category>
      <category>ldap</category>
    </item>
    <item>
      <title>How to Download and Install PowerShell 7 on Windows, Linux, and macOS</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 20:34:32 +0000</pubDate>
      <link>https://dev.to/adbertram/how-to-download-and-install-powershell-7-on-windows-linux-and-macos-40ml</link>
      <guid>https://dev.to/adbertram/how-to-download-and-install-powershell-7-on-windows-linux-and-macos-40ml</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Jeff Christman. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/jeff"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nowadays, sysadmins are required to know more than one operating system. That used to mean learning more than a few shell scripting languages. PowerShell Core and by downloading and installing PowerShell 7, you can change that.&lt;/p&gt;

&lt;p&gt;It's no longer necessary to learn a new scripting language to support heterogeneous environments with PowerShell 7. PowerShell 7 is cross-platform meaning it can be installed on Windows, Linux, and other operating systems.&lt;/p&gt;

&lt;p&gt;PowerShell 7 is supported on Windows, macOS, and Linux. It's open-source, built for heterogeneous environments and the hybrid cloud. PowerShell Core has recently become available on Windows internet of things (IoT) as well.&lt;/p&gt;

&lt;p&gt;The cross-platform nature of PowerShell 7 means that the scripts that you write will run on any supported operating system.&lt;/p&gt;

&lt;h2 id="powershell-core-vs-windows-powershell"&gt;PowerShell 7 vs. Windows PowerShell&lt;/h2&gt;

&lt;p&gt;The main difference between Windows PowerShell and PowerShell 7 is the platform they are built on.&lt;/p&gt;

&lt;p&gt;Windows PowerShell is built on top of the .NET frameWork. Because of that dependency, it's only available on Windows and is launched as &lt;em&gt;powershell.exe&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;Note that you can run PowerShell 7 and Windows PowerShell side by side. In fact, there is no way to completely remove Windows PowerShell anyway.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;PowerShell 7 is built on .NET Core and is available cross-platform and is launched as &lt;em&gt;pswh.exe&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id="installing-powershell-core"&gt;Installing PowerShell 7&lt;/h2&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;This tutorial will focus on installing the latest release of PowerShell 7 at the time of this writing which is PowerShell 7.1.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;There are different processes to get PowerShell 7 installed on various operating systems. Let's cover each one.&lt;/p&gt;

&lt;h3 id="windows"&gt;Windows&lt;/h3&gt;

&lt;p&gt;To install PowerShell 7 on Windows, navigate to the PowerShell &lt;a href="https://github.com/PowerShell/PowerShell/releases"&gt;GitHub repository&lt;/a&gt; and download the &lt;em&gt;.msi&lt;/em&gt; package appropriate for your system. Then run through the wizard. Easy peasy.&lt;/p&gt;

&lt;h3 id="windows-iot"&gt;Windows IoT&lt;/h3&gt;

&lt;p&gt;Windows Iot devices already have PowerShell installed which we will use for installing Powershell Core&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-powershell"&gt;
#Enter a psession to the device
$s = New-PSSession -ComputerName &amp;lt;deviceIp&amp;gt; -Credential Administrator

#Copy the file to the device
&lt;a class="wpil_keyword_link" title="Copy-Item" href="https://adamtheautomator.com/copy-item-copying-files-powershell/"&gt;Copy-Item&lt;/a&gt; .\PowerShell-7.1.0-win-x64.msi -Destination u:\users\administrator\Downloads -ToSession $s

#Connect to the device and expand the archive
Enter-PSSession $s
Set-Location u:\users\administrator\downloads
Expand-Archive .\PowerShell-7.1.0-win-x64.zip

#Setup Remoting
Set-Location .\PowerShell-7.1.0-win-x64

# Be sure to use the -PowerShellHome parameter otherwise it'll try to create a new endpoint with Windows PowerShell 5.1
.\Install-PowerShellRemoting.ps1 -PowerShellHome .

# You'll get an error message and will be disconnected from the device because it has to restart WinRM
# Connect to the device. Be sure to use the -Configuration parameter. If you omit it, you will connect to Windows PowerShell 5.1
Enter-PSSession -ComputerName &amp;lt;deviceIp&amp;gt; -Credential Administrator -Configuration powershell.7.1.0&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once in the session, you should then be running PowerShell on the IoT device!&lt;/p&gt;

&lt;h3 id="ubuntu-debian"&gt;Ubuntu, Debian&lt;/h3&gt;

&lt;p&gt;For Linux distributions, it just a matter of adding the repository and installing with the package manager.&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-bash"&gt;
# Download the Microsoft repository GPG keys
wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb

# Register the Microsoft repository GPG keys
sudo dpkg -i packages-microsoft-prod.deb

# Update the list of products
sudo apt-get update

# Install PowerShell
sudo apt-get install -y powershell

# Start PowerShell
pwsh&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="centos-and-redhat"&gt;CentOS and RedHat&lt;/h3&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-bash"&gt;
# Register the Microsoft RedHat repository
curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo

# Install PowerShell
sudo yum install -y powershell

# Start PowerShell
pwsh&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="opensuse"&gt;OpenSUSE&lt;/h3&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-bash"&gt;# Register the Microsoft signature key
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc

# Add the Microsoft Repository
zypper ar https://packages.microsoft.com/rhel/7/prod/

# Update the list of products
sudo zypper update

# Download and Install PowerShell 7
sudo zypper install powershell

# Start PowerShell
pwsh&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="fedora"&gt;Fedora&lt;/h3&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-bash"&gt;# Register the Microsoft signature key
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc

# Register the Microsoft RedHat repository
curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo

# Update the list of products
sudo dnf update

# Install a system component
sudo dnf install compat-openssl10

# Download and Install PowerShell 7
sudo dnf install -y powershell

# Start PowerShell
pwsh&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="macos"&gt;MacOS&lt;/h3&gt;

&lt;p&gt;For macOS, Homebrew is the preferred package manager. Installing Homebrew package manager is a single line command from a terminal, then install Powershell Core.&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-bash"&gt;
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

#install PowerShell Core
brew cask install powershell

#start session
pwsh&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;

&lt;p&gt;Learn to manage different platforms and OSs. It'll only help your career. When you need a cross-platform scripting language, try out PowerShell Core to write once and deploy everywhere. It's another tool in your toolbox.&lt;/p&gt;

&lt;p&gt;If you don't learn it, someone else will.&lt;/p&gt;

</description>
      <category>powershell</category>
    </item>
    <item>
      <title>How to Convert Text to Speech with PowerShell</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 20:29:52 +0000</pubDate>
      <link>https://dev.to/adbertram/how-to-convert-text-to-speech-with-powershell-2227</link>
      <guid>https://dev.to/adbertram/how-to-convert-text-to-speech-with-powershell-2227</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Francisco Navarro. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/francisco" rel="noopener noreferrer"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com" rel="noopener noreferrer"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of PowerShell’s core design principles is to allow the end-user to jump right in with little to no shell experience and hit the ground running. However, PowerShell can let you get creative too. For example, did you know you can convert text to speech with PowerShell?&lt;/p&gt;

&lt;p&gt;In this article, you will learn how to have PowerShell speak, switch to available voices, and learn how to use PowerShell's speech capabilities to add notification mechanisms to PowerShell script.&lt;/p&gt;

&lt;h2 id="h-prerequisites"&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;This article is built as a walkthrough. If you'd like to follow along, please be sure you're running Windows 10. It has everything you need already installed.&lt;/p&gt;

&lt;h2 id="h-preparing-text-to-speech-in-powershell"&gt;Preparing Text to Speech in PowerShell&lt;/h2&gt;

&lt;p&gt;PowerShell, out of the box, won't just begin talking to you. You'll first have to prepare for it. Doing so requires two steps; adding the correct .NET assembly and creating the appropriate .NET object.&lt;/p&gt;

&lt;h3 id="h-adding-the-system-speech-net-assembly"&gt;Adding the &lt;code&gt;System.Speech&lt;/code&gt; .NET Assembly&lt;/h3&gt;

&lt;p&gt;Since PowerShell doesn't come with native cmdlets to convert text to speech, you must first dig into .NET using a specific .NET assembly called &lt;code&gt;System.Speech&lt;/code&gt;. The &lt;code&gt;System.Speech&lt;/code&gt; assembly is where the  &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.speech.synthesis.speechsynthesizer" rel="noopener noreferrer"&gt;.NET SpeechSynthesizer class&lt;/a&gt; lives.  This .NET class will allow you to convert text to speech.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The SpeechSynthesizer class is native to the Windows .NET framework. It is not available in PowerShell versions 6+.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To make the SpeechSythensizer class available, you will load the &lt;code&gt;System.Speeech&lt;/code&gt; assembly using the &lt;code&gt;Add-Type&lt;/code&gt; cmdlet as shown below.&lt;/p&gt;

&lt;p&gt;When you have a Windows PowerShell console open as administrator, insert the following code snippet.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; Add-Type -AssemblyName System.Speech&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You might have noticed there was no confirmation of any sorts when running the above code. Even by adding the &lt;code&gt;Verbose&lt;/code&gt; parameter, you will see no output. Don't worry, everything is fine. This is the default behavior of the &lt;code&gt;Add-Type&lt;/code&gt; cmdlet.&lt;/p&gt;

&lt;p&gt;To ensure the assembly imported, invoke a .NET class that is part of the assembly. For example, since you'll be working with the &lt;code&gt;SpeechSynthesizer&lt;/code&gt; class, provide the entire path to the &lt;code&gt;SpeecSynthesizer&lt;/code&gt; class enclosed in brackets as shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; [System.Speech.Synthesis.SpeechSynthesizer]
 
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    SpeechSynthesizer                        System.Object&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If all is well, you will see the &lt;code&gt;SpeechSynthesizer&lt;/code&gt; object returned.&lt;/p&gt;

&lt;p&gt;You have now successfully imported the &lt;code&gt;System.Speech&lt;/code&gt; assembly. Next, let's see how you can make the assembly work for you.&lt;/p&gt;

&lt;h3 id="h-creating-a-speechsynthesizer-net-object"&gt;Creating a &lt;code&gt;SpeechSynthesizer&lt;/code&gt; .NET Object&lt;/h3&gt;

&lt;p&gt;With the &lt;code&gt;System.Speech&lt;/code&gt; assembly, you now have access to the .NET &lt;code&gt;SpeechSynthesizer&lt;/code&gt; class. To invoke the class, you need to create an object first. To do that, use the &lt;code&gt;New-Object&lt;/code&gt; cmdlet as shown below. The code below is assigning that object to the &lt;code&gt;ATAVoiceEngine&lt;/code&gt; variable which you will use in a little bit.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine = New-Object System.Speech.Synthesis.SpeechSynthesizer&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once you've created the object and assigned it to a variable, confirm that the variable contains the object you expect. If the output looks like the following code snippet, you're good to go. You're one step closer to converting text to speech in PowerShell!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine

State Rate Volume Voice
----- ---- ------ -----
Ready    0    100 System.Speech.Synthesis.VoiceInfo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you're curious, you can also take a look at all of the various members on the object by using &lt;code&gt;Get-Member&lt;/code&gt;. Note the methods available on this object. You'll be using these in a bit.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine | Get-Member&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="h-converting-text-to-speech-with-powershell"&gt;Converting Text to Speech with PowerShell&lt;/h2&gt;

&lt;p&gt;Now that you have an object capable of converting text to speech, let's try it out. To do so, you'll use the &lt;code&gt;Speak()&lt;/code&gt; method on the object.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;Get-Member&lt;/code&gt; to inspect the members on the object, notice on method in particular called &lt;code&gt;Speak()&lt;/code&gt; as shown below. When you pass text to this method, PowerShell will in turn use .NET to convert that text to speech.&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%2Fu50ekm8jfzm2wd019ppt.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%2Fi%2Fu50ekm8jfzm2wd019ppt.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take the default text-to-speech conversion for a spin with all of the default settings. Be sure your sound and speakers are on and have PowerShell convert the phrase "Hello Adam The Automator audience. How are you doing today?" to voice. To do that, simply pass that phrase to the &lt;code&gt;Speak()&lt;/code&gt; method as shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $mytext = "Hello Adam The Automator audience. How are you doing today?"
PS51&amp;gt; $ATAVoiceEngine.Speak($mytext)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Feel free to change up the text as much as you want just keep it clean!&lt;/p&gt;

&lt;h2 id="h-changing-up-the-voice"&gt;Changing up the Voice&lt;/h2&gt;

&lt;p&gt;By default, the &lt;code&gt;Speak()&lt;/code&gt; method will use a default voice called &lt;a href="https://mssam.fandom.com/wiki/Microsoft_David_(Desktop)#:~:text=Microsoft%20David%20(or%20%22Microsoft%20David,Windows%208%20and%20Windows%2010." rel="noopener noreferrer"&gt;Microsoft David&lt;/a&gt;. However, you can use many different voices if David doesn't meet your liking.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;You might recall that Microsoft David is the default voice in the Windows Narrator feature.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="h-inspecting-the-voice-object"&gt;Inspecting the Voice Object&lt;/h3&gt;

&lt;p&gt;Take a look at some of the common properties of the &lt;code&gt;System.Speech.Synthesis.SpeechSynthesizer&lt;/code&gt; object. One interesting property is the &lt;code&gt;Voice&lt;/code&gt; property. This property allows you to change up the voice used in speech. Notice that this is an embedded object of type &lt;code&gt;System.Speech.Synthesis.VoiceInfo&lt;/code&gt; that will have its own set of properties.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt;  $ATAVoiceEngine | Format-Table -properties *

State Rate Volume Voice
----- ---- ------ -----
Ready    0    100 System.Speech.Synthesis.VoiceInfo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Specifically looking at the &lt;code&gt;Voice&lt;/code&gt; property, you can see various attributes as seen below.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt;  $ATAVoiceEngine.Voice

Gender                : Male
Age                   : Adult
Name                  : Microsoft David Desktop
Culture               : en-US
Id                    : TTS_MS_EN-US_DAVID_11.0
Description           : Microsoft David Desktop - English (United States)
SupportedAudioFormats : {}
AdditionalInfo        : {[Age, Adult], [Gender, Male], [Language, 409], [Name, Microsoft David Desktop]...}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's change the voice that dictates your text.&lt;/p&gt;

&lt;h3 id="h-discovering-available-voices"&gt;Discovering Available Voices&lt;/h3&gt;

&lt;p&gt;Let's see what other voices are available. To get a list of voices, call the &lt;code&gt;GetInstalledVoices()&lt;/code&gt; method on the object as shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine.GetInstalledVoices()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will see some &lt;code&gt;VoiceInfo&lt;/code&gt; objects returned below. You can see that two objects are returned. These objects tell you that two different voices are currently installed. You may have more voices available.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine.GetInstalledVoices()

VoiceInfo                         Enabled
---------                         -------
System.Speech.Synthesis.VoiceInfo    True
System.Speech.Synthesis.VoiceInfo    True
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Dig a little bit deeper to find the actual list of voices as properties on each of &lt;code&gt;VoiceInfo&lt;/code&gt; objects by passing each of the &lt;code&gt;VoiceInfo&lt;/code&gt; objects that the &lt;code&gt;GetInstalledVoices()&lt;/code&gt; method returns to &lt;code&gt;Get-Member&lt;/code&gt;. Notice the &lt;code&gt;VoiceInfo&lt;/code&gt; property's &lt;em&gt;Definition&lt;/em&gt; displays the &lt;code&gt;System.Speech.Synthesis.VoiceInfo&lt;/code&gt; class and &lt;code&gt;VoiceInfo&lt;/code&gt; class.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS&amp;gt; $ATAVoiceEngine.GetInstalledVoices() | Get-Member

   TypeName: System.Speech.Synthesis.InstalledVoice

Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
ToString    Method     string ToString()
Enabled     Property   bool Enabled {get;set;}
VoiceInfo   Property   System.Speech.Synthesis.VoiceInfo VoiceInfo {get;} 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By referencing the &lt;code&gt;VoiceInfo&lt;/code&gt; property on each of the objects as shown below, you can get more information about each voice. Below you can see that this computer has &lt;code&gt;Microsoft David Desktop&lt;/code&gt; and &lt;code&gt;Microsoft Zira Desktop&lt;/code&gt; voices available.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine.GetInstalledVoices().VoiceInfo   

Gender                : Male
Age                   : Adult
Name                  : Microsoft David Desktop
Culture               : en-US
Id                    : TTS_MS_EN-US_DAVID_11.0
Description           : Microsoft David Desktop - English (United States)
SupportedAudioFormats : {}
AdditionalInfo        : {[Age, Adult], [Gender, Male], [Language, 409], [Name, Microsoft David Desktop]...}

Gender                : Female
Age                   : Adult
Name                  : Microsoft Zira Desktop
Culture               : en-US
Id                    : TTS_MS_EN-US_ZIRA_11.0
Description           : Microsoft Zira Desktop - English (United States)
SupportedAudioFormats : {}
AdditionalInfo        : {[Age, Adult], [Gender, Female], [Language, 409], [Name, Microsoft Zira Desktop]...}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="h-setting-the-text-to-speech-voice"&gt;Setting the Text-to-Speech Voice&lt;/h3&gt;

&lt;p&gt;Once you know the available voices, you can then change them by using the &lt;code&gt;SelectVoice()&lt;/code&gt; method on the &lt;code&gt;SpeechSynthesizer&lt;/code&gt; object. Below you can see how to change the voice to Microsoft Zira Desktop.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine.SelectVoice("Microsoft Zira Desktop")&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once the new voice is set, you can then use the &lt;code&gt;Speak()&lt;/code&gt; method again passing whatever text you'd like to convert text to speech again using the new voice.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine.Speak("Hello, My name is Zira. PowerShell 7's older sibling?")&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="h-freeing-up-the-console-while-converting-text-to-speech-with-powershell"&gt;Freeing up the Console While Converting Text to Speech with PowerShell&lt;/h2&gt;

&lt;p&gt;You may have noticed when passing text to the speech engine that PowerShell does not allow you to do anything else while speech is happening. This may be problematic if you need to recite a lot of text and still need to perform some other tasks. Instead of using the &lt;code&gt;Speak()&lt;/code&gt; method, you can use the &lt;code&gt;SpeakAsync()&lt;/code&gt; method instead.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SpeakAsync()&lt;/code&gt; method frees up the console as soon as the text-to-speech conversion starts. To use the &lt;code&gt;SpeakAsync()&lt;/code&gt; method, simply replace this method with &lt;code&gt;Speak()&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PS51&amp;gt; $ATAVoiceEngine.SpeakAsync('Hello, my name is Zira. How are you doing today?')&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will see that PowerShell frees up the console as soon as you hit Enter.&lt;/p&gt;

&lt;h2 id="h-building-your-own-assistant"&gt;Building your Own Assistant&lt;/h2&gt;

&lt;p&gt;So far you have instantiated a .NET class and turned it into a Windows PowerShell object you can leverage. You had some fun with the &lt;code&gt;$ATAVoiceEngine&lt;/code&gt; object and learned to manipulate its settings, including changing the voice. Now let's leverage the object to be a notification mechanism in your PowerShell Scripts.&lt;/p&gt;

&lt;p&gt;By combining a typical PowerShell script with the text-to-speech ability, your computer can vocally tell you what's going on! Below is a good example. When you run this script the engine calls out the name of the service and its status.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;### In honor of PowerShell 7's avatar we select the "Zira Desktop Voice"  
$ATAVoiceEngine.SelectVoice("Microsoft Zira Desktop")

### Fetching a list of Windows Services which names start with "W" selecting the first 5
$services = Get-Service W* | Select-Object -First 5

### Create a &lt;a href="https://adamtheautomator.com/powershell-foreach/" title="ForEach" rel="noopener noreferrer"&gt;ForEach&lt;/a&gt; Loop for those services
ForEach ($service in $services) {
    ### Using the speech engine object $ATAVoiceEngine with the "SpeakAsync" method
    $ATAVoiceEngine.SpeakAsync("The Service $($service.displayname) is $($service.status)") | Out-Null
} &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can run a script like this in the background, against a server or workstations and find out if your critical services are running or not.&lt;/p&gt;

&lt;h2 id="h-summary"&gt;Summary&lt;/h2&gt;

&lt;p&gt;In this article, you've discovered how to use the .NET framework and PowerShell to convert text to speech by changing up various voices and how to even integrate text-to-speech into your PowerShell scripts. Go ahead, get creative, and build your own PowerShell assistant!&lt;/p&gt;

&lt;h3 id="h-further-reading"&gt;Further Reading:&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Docs published a &lt;a href="https://docs.microsoft.com/en-us/dotnet/api/?view=netframework-4.8&amp;amp;term=system.speech" rel="noopener noreferrer"&gt;.NET API Browser&lt;/a&gt;. It’s a great resource to reference when searching for .NET Assemblies, Namespaces, and Classes.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.speech.synthesis.speechsynthesizer?view=netframework-4.8" rel="noopener noreferrer"&gt;SpeechSynthesizer Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.speech.synthesis.speechsynthesizer.speak?view=netframework-4.8" rel="noopener noreferrer"&gt;SpeechSynthesizer.Speak Method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/api/system.speech.synthesis.speechsynthesizer.selectvoice?view=netframework-4.8" rel="noopener noreferrer"&gt;SpeechSynthesizer.SelectVoice(String) Method&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>powershell</category>
    </item>
    <item>
      <title>Managing File Shares with PowerShell</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 20:26:03 +0000</pubDate>
      <link>https://dev.to/adbertram/managing-file-shares-with-powershell-5e4</link>
      <guid>https://dev.to/adbertram/managing-file-shares-with-powershell-5e4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by &lt;a href="https://adamtheautomator.com/author/david/"&gt;David Lamb&lt;/a&gt;. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/david"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can be challenging to keep track of just what file shares have been set up in your environment. This becomes even more difficult if you have to track this information across multiple servers. Adding to the tedium is remotely connecting to each server to find the list the shares. Thankfully, using &lt;a href="https://adamtheautomator.com/how-to-set-up-and-manage-scheduled-tasks-with-powershell/"&gt;PowerShell get-smbshare makes this task&lt;/a&gt; a snap, whether you need to enumerate shares on just one server, or many.&lt;/p&gt;

&lt;blockquote class="wp-block-quote"&gt;&lt;p&gt;&lt;em&gt;If you're a beginner/intermediate PowerShell scripter, be sure to check out my &lt;a href="https://adamtheautomator.com/powershell-tutorial-mini-course/"&gt;FREE mini-course on Building a PowerShell Tool&lt;/a&gt;! 9,000+ words of deep explanation and insights on how to build a PowerShell tool.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;This blog post has a companion video created by &lt;a href="https://techsnips.io/contributors/david-lamb/"&gt;TechSnips contributor, David Lamb&lt;/a&gt;. Feel free to have a watch or, if you prefer text, read on!&lt;/p&gt;

&lt;h2 id="enumerate-shares-on-a-single-file-server"&gt;Enumerate Shares on a Single File Server&lt;/h2&gt;

&lt;p&gt;Let's start by connecting to a remote file server to gather this information from a single server. We will accomplish this by entering into a remote PowerShell session with our file server &lt;em&gt;FILE01&lt;/em&gt;.&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-powershell"&gt;PS&amp;gt; Enter-PSSession -ComputerName FILE01&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once connected, it takes a single cmdlet to get file share information get-smbshare.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K7CX1nKq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0qn5khqafdvnh5dx7bk7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K7CX1nKq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0qn5khqafdvnh5dx7bk7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, this gives us a list of all of the share on this server. This also includes the administrative shares, whose share names are appended by &lt;code&gt;$&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This does accomplish the task of getting a list of shares, but it is a little cluttered. We can clean up this list by using the &lt;code&gt;Special&lt;/code&gt; parameter and setting it to &lt;code&gt;$false&lt;/code&gt; to specify that we do not wish to see the administrative shares:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S_Gq8V5f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nufme1cne1c9swfc2s6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S_Gq8V5f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/nufme1cne1c9swfc2s6l.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There, that gives us a much clearer view of the share information we are looking for.&lt;/p&gt;

&lt;p&gt;Now that we have our share on this server identified, it might be useful to list all of the properties for this share, especially if we are looking for specific details about our share:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Htb4X0WM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ww20kd56l106nxpk0ofx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Htb4X0WM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ww20kd56l106nxpk0ofx.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows us to view quite a bit of information about our share, including things like the type of share, folder enumeration mode, caching mode, and of course, our share name and path, to name a few.&lt;/p&gt;

&lt;p&gt;It is also possible to view the share permissions for this share by switching to the get-smbshare access cmdlet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rOleqB8O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ug44yjy45yzm6omeu46y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rOleqB8O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ug44yjy45yzm6omeu46y.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gives us a list of the users and groups, and their current level of access to the share.&lt;/p&gt;

&lt;p&gt;We might also have a time where we need to enumerate the share permissions to find out who has full access to a share:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---sqtjewV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dstehk64yeapxks9hhai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---sqtjewV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dstehk64yeapxks9hhai.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this information, it is easy to tell who has full access to the share and then take steps to remove that access if it isn't appropriate for an individual or group.&lt;/p&gt;

&lt;p&gt;Now that we are done enumerating shares on a single server, we need to make sure we close our remote PowerShell session:&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-powershell"&gt;PS&amp;gt; Exit-PSSession&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="enumerate-shares-on-multiple-file-servers"&gt;Enumerate Shares on Multiple File Servers&lt;/h2&gt;

&lt;p&gt;It is also possible to retrieve this same information from multiple file servers, which is an area where PowerShell really shines.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;&lt;a class="wpil_keyword_link" href="https://adamtheautomator.com/invoke-command-remote/" title="Invoke-Command"&gt;Invoke-Command&lt;/a&gt;&lt;/code&gt; to run get-smbshare, we can list the shares on both the &lt;em&gt;FILE01&lt;/em&gt; and &lt;em&gt;FILE02&lt;/em&gt; servers. If we also pipe the output through &lt;code&gt;Format-Table&lt;/code&gt;, we can also get a nice organized list:&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-powershell"&gt;PS&amp;gt; Invoke-Command -ComputerName "FILE01","FILE02" -ScriptBlock {Get-SmbShare} | Format-Table -Property Name,Path,Description,PSComputerName&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uEUGg_02--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0zj36c5oqq1vc51odtg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uEUGg_02--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0zj36c5oqq1vc51odtg2.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While entering the file server names manually is fine if there are only two or three servers, it becomes tedious if there are many dozens of servers to check. To get around this, we can assign the output of &lt;code&gt;&lt;a class="wpil_keyword_link" href="https://adamtheautomator.com/get-adcomputer-powershell/" title="Get-ADComputer"&gt;Get-ADComputer&lt;/a&gt;&lt;/code&gt; to the variable &lt;code&gt;$FileServAD&lt;/code&gt; and get a list of all servers in the &lt;em&gt;File Servers&lt;/em&gt; organizational unit (OU). From there, it's easy to get the information:&lt;/p&gt;

&lt;pre class="wp-block-code"&gt;&lt;code class="language-powershell"&gt;$FileServAD = Get-ADComputer -SearchBase "OU=File Servers,OU=Servers,DC=corp,dc=ad" -Filter * |
Select-Object -ExpandProperty Name

Invoke-Command -ComputerName $FileServAD -ScriptBlock {
    Get-SmbShare -Special $false
} | Format-Table -Property Name,Path,Description,PSComputerName&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gjPjm_Fl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9wpfnkthhuptkt1bjl1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gjPjm_Fl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/9wpfnkthhuptkt1bjl1e.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There we have it! A nice tidy list of all of the file shares on all of our file servers.&lt;/p&gt;

&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;

&lt;p&gt;In this blog, you have learned how to enumerate file shares using the get-smbshare cmdlet.&lt;/p&gt;

</description>
      <category>powershell</category>
    </item>
    <item>
      <title>How to Create GPOs (Starter GPOs) for Quick Policy Baselines</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 19:40:45 +0000</pubDate>
      <link>https://dev.to/adbertram/how-to-create-gpos-starter-gpos-for-quick-policy-baselines-1epo</link>
      <guid>https://dev.to/adbertram/how-to-create-gpos-starter-gpos-for-quick-policy-baselines-1epo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Bill Kindle. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/bill"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GPOs (Starter GPOs, actually) are a feature in Active Directory that allows you to build templates for common GPOs. In this blog, you're going to get a glimpse into how starter GPOs can help you speed up GPO-creation.&lt;/p&gt;

&lt;p&gt;If you are lucky to build a complete Active Directory infrastructure from scratch, then you know how much planning and consideration goes into the whole process. And it doesn't just stop with delivering the environment. You have to also consider ongoing management of the environment. That's why you should consider using &lt;em&gt;starter group policy objects&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id="what-are-starter-gpos"&gt;What are Starter GPOs?&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;starter group policy object&lt;/em&gt; is a blank, or &lt;em&gt;clean slate&lt;/em&gt; &lt;em&gt;group policy object&lt;/em&gt;. The purpose of these objects is to allow an administrator to create and have a pre-configured group of settings that represent a baseline for any future policy that is to be created.&lt;/p&gt;

&lt;p&gt;These settings can then be copied into a more "formal" &lt;em&gt;group policy object&lt;/em&gt; to then be applied to single or multiple &lt;em&gt;organizational units&lt;/em&gt;. Copying these starter objects preserves your baseline strategy and allows you to dynamically add or remove settings that shouldn't be applied to future objects.&lt;/p&gt;

&lt;p&gt;These objects are great for settings that will not be changing, such as specific security related protocol configurations, &lt;em&gt;Windows Update&lt;/em&gt; settings, particular software settings or registry entries to name a few. The choice is yours as a sysadmin and can reflect whatever strategy you are employing.&lt;/p&gt;

&lt;h2 id="using-starter-gpos-with-powershell-a-story"&gt;Using Starter GPOs with PowerShell: A Story&lt;/h2&gt;

&lt;p&gt;You walk into your work area and just as you start sipping that already cold cup of coffee because you've been stopped 15 times on your way to your area, you open your email to discover you are being asked by the boss to aid in deploying &lt;em&gt;group policy&lt;/em&gt; in an environment. You are also given a list of baselines required for this new deployment from your security team or boss.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; begin assigning these baselines to ordinary policy objects using the &lt;em&gt;Group Policy Management Console&lt;/em&gt; or because you are a long-term thinker, you decide to step back and see if you can maybe automate some of this task and memorialize these baselines better.&lt;/p&gt;

&lt;p&gt;You open up your trusty PowerShell console and start looking for cmdlets. You then search for the module &lt;em&gt;GroupPolicy&lt;/em&gt; and import it. You would have this module installed if you have already downloaded and installed &lt;a href="https://www.microsoft.com/en-us/download/details.aspx?id=45520"&gt;RSAT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking at the available cmdlets, you find two that look like they are exactly what you need, &lt;code&gt;New-GPStarterGPO&lt;/code&gt; and &lt;code&gt;Get-GPStarterGPO&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After looking at the help you see that creating this one by one or even as a loop is pretty straightforward and there are only but a couple of necessary parameters called &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;Comment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vywIS_ay--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3fb1yuibj5ea2b3yrqwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vywIS_ay--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3fb1yuibj5ea2b3yrqwr.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Armed with this new info, you create a small &lt;em&gt;&lt;a class="wpil_keyword_link" href="https://adamtheautomator.com/powershell-foreach/" title="foreach"&gt;foreach&lt;/a&gt;&lt;/em&gt; loop through an array of names &amp;amp; comments and pass them off to the &lt;code&gt;New-GPStarterGPO&lt;/code&gt; cmdlet, creating 5 new baseline policies to be edited later by your intern. Time for a beverage refill.&lt;/p&gt;

&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;

&lt;p&gt;GPOs (Starter GPOs) are GPO templates that allow you to save time as a sysadmin. Using PowerShell cmdlets like &lt;code&gt;New-GPStarterGPO&lt;/code&gt; and &lt;code&gt;Get-SPStarterGPO&lt;/code&gt; allow you to save even more time by quickly creating these starter GPOs directly from the command line!&lt;/p&gt;

&lt;p&gt;If you'd like to learn more about GPOs, check out &lt;a href="https://adamtheautomator.com/tag/group-policy-objects/"&gt;all of the other GPO posts we have available here on the blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>activedirectory</category>
      <category>grouppolicy</category>
    </item>
    <item>
      <title>Writing an Extension Vault for PowerShell SecretManagement Preview 4</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 19:37:53 +0000</pubDate>
      <link>https://dev.to/adbertram/writing-an-extension-vault-for-powershell-secretmanagement-preview-4-19o6</link>
      <guid>https://dev.to/adbertram/writing-an-extension-vault-for-powershell-secretmanagement-preview-4-19o6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was written by Adam Listek. He is a frequent contributor to the Adam the Automator (ATA) blog. If you'd like to read more from this author, check out his &lt;a href="http://adamtheautomator.com/author/alistek" rel="noopener noreferrer"&gt;ATA author page&lt;/a&gt;. Be sure to also check out &lt;a href="https://adamtheautomator.com" rel="noopener noreferrer"&gt;more how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've ever hardcoded a password, an API key, or a private certificate in a script, stop! You need to secure that sensitive information! One way to do that is with the PowerShell &lt;a href="https://github.com/PowerShell/SecretManagement" rel="noopener noreferrer"&gt;SecretManagement&lt;/a&gt; module. Offering a convenient way for a user to store and retrieve secrets using PowerShell, the SecretManagement module also has the ability to interface with different back-end systems such as Azure KeyStore or KeePass too using an &lt;a href="https://github.com/PowerShell/SecretManagement/tree/master/ExtensionModules" rel="noopener noreferrer"&gt;extension vault&lt;/a&gt;!&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;An extension vault is a term used within the SecretManagement module to refer to a nested PowerShell module used within the SecretManagement module. You'll also hear vaults referred to as extension modules also.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;In this article, we create an extension vault that is backed by the open-source .NET database &lt;a href="https://www.litedb.org/" rel="noopener noreferrer"&gt;LiteDB&lt;/a&gt;. An extension module can use any back-end, but using LiteDB allows us to use a .NET native simple database that demonstrates the SecretManagement module capabilities and how to interact with different systems.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Note: Don't get confused with the "secret" in the name. Even though this module is meant to be used for "secret" management, as you'll see in this tutorial, it really could be used as a frontend or just about any data source, if you wish.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="h-prerequisites"&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;This article is a tutorial. If you'd like to follow along, please be sure you have the following prerequisites in order.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://adamtheautomator.com/updating-to-powershell-7/" rel="noopener noreferrer"&gt;PowerShell 7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Powershell SecretManagement module - You can install this module by running &lt;code&gt;Install-Module -Name 'Microsoft.PowerShell.SecretManagement' -AllowPrerelease&lt;/code&gt;. This tutorial will be using the most current version at the time of writing which is Preview 4.&lt;/li&gt;
&lt;li&gt;The LiteDB database - Though you can create any backing store for your secret database, this article demonstrates creating an extension module to leverage LiteDB in the background. You can install LiteDB by running &lt;code&gt;Install-Package -Name 'LiteDB' -MinimumVersion '5.0.9' -Source 'nuget.org' -Scope 'CurrentUser'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="h-understanding-the-powershell-secretmanagement-module"&gt;Understanding the PowerShell SecretManagement Module&lt;/h2&gt;

&lt;p&gt;The PowerShell SecretManagement module is a &lt;a href="https://adamtheautomator.com/powershell-modules/" title="PowerShell module" rel="noopener noreferrer"&gt;PowerShell module&lt;/a&gt; that provides a standardized, Microsoft-supported way of interacting with sensitive information. Think of this module as a better way of storing and retrieving sensitive information than using &lt;code&gt;Get-Credential&lt;/code&gt; or perhaps storing &lt;a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/convertto-securestring?view=powershell-7" rel="noopener noreferrer"&gt;secure strings&lt;/a&gt; in common text files.&lt;/p&gt;

&lt;p&gt;This module has two main object types; secrets and vaults. A vault is a data store that stores secrets. To store and retrieve secrets, you must first &lt;em&gt;register&lt;/em&gt; a vault to then add secrets to that vault. You can then &lt;em&gt;unregister&lt;/em&gt; that vault if you no longer need to store secrets in that data source.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;When you install the module, there are no actual vaults available. If you want to install the previously default extension vault, run &lt;code&gt;Install-Module -Name 'Microsoft.PowerShell.SecretStore' -AllowPrerelease&lt;/code&gt;. Otherwise, keep reading as you'll learn how to create a custom one with LiteDb.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;You'll find a few different cmdlets in the module to interact with the various objects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Get-Secret&lt;/code&gt; - Retrieve a secret from a vault.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Get-SecretInfo&lt;/code&gt; - Retrieve information about one or more secrets in a vault.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Get-SecretVault&lt;/code&gt; - Retrieve information about a specific vault.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Register-SecretVault&lt;/code&gt; - Register a new secret vault.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Remove-Secret&lt;/code&gt; - Remove a secret from a vault.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Set-Secret&lt;/code&gt; - Create a new secret or update an existing secret in a vault.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Test-SecretVault&lt;/code&gt; - Verify that a vault is correctly configured.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Unregister-SecretVault&lt;/code&gt; - Unregister a previously registered vault.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To actually make any of the above cmdlets functional, you need to back them with a vault. To do that, the SecretManagement module requires you to build your own functions with the same name essentially overwriting the default cmdlets.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;If your extension does not offer the ability to add or update a secret, you could implement the function but only throw an error message stating that it is not supported.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Next, we will explore how to structure a PowerShell SecretManagement extension vault and start to implement the commands.&lt;/p&gt;

&lt;h2 id="h-creating-a-powershell-secretmanagement-extension-vault"&gt;Creating a PowerShell SecretManagement Extension Vault&lt;/h2&gt;

&lt;p&gt;Now that you have seen the basic cmdlet structure and objects you'll be working with, let's now learn how to create an example extension vault!&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;The root directory to create the first parent module directory is in the SecretManagement module's ExtensionModules folder. You can find this directory by running &lt;code&gt;(Get-Module -Name Microsoft.PowerShell.SecretManagement).Path | Split-Path -Parent&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="h-create-the-main-vault-folder-and-manifest"&gt;Create the Main Vault Folder and Manifest&lt;/h3&gt;

&lt;ol&gt;&lt;li&gt;Open up PowerShell and create a variable holding the path to the &lt;em&gt;ExtensionsModule&lt;/em&gt; parent directory which all underlying module folders and files will be created. &lt;/li&gt;&lt;/ol&gt;

&lt;pre&gt;&lt;code&gt;$vaultParentPath = (Get-Module -Name Microsoft.PowerShell.SecretManagement).Path | Split-Path -Parent. &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;2. Create the parent module directory in the &lt;em&gt;ExtensionModules&lt;/em&gt; directory. Name this directory &lt;em&gt;SecretManagement.&amp;lt;Vault source&amp;gt;&lt;/em&gt;. In this case, the directory is called &lt;em&gt;SecretManagement.LiteDB.&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;New-Item -Path "$vaultParentPath\SecretManagement.LiteDb" -ItemType Directory&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;3. Create a module manifest for the &lt;em&gt;SecretManagement.LiteDb&lt;/em&gt; module outlining some common manifest attributes.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;New-ModuleManifest -CompatiblePSEditions Core -Author '&amp;lt;you&amp;gt;' -NestedModules './SecretManagement.LiteDB.Extension/SecretManagement.LiteDB.Extension.psd1' -RequiredModules 'Microsoft.Powershell.SecretManagement' -PowerShellVersion 7.0 -Tags 'SecretManagement' -Path "$vaultParentPath\SecretManagement.LiteDb"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;4. Once New-ModuleManifest creates the template manifest, open it up and ensure it mirrors the below manifest.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@{
  ModuleVersion        = '0.0.0.1'
  # Make this only compatible with PowerShell Core/7
  CompatiblePSEditions = @('Core')
  GUID                 = '&amp;lt;guid here&amp;gt;' ## Generate a GUID with New-Guid
  Author               = 'Adam Listek'
  # Pointed to the nested module definition file
  NestedModules   = @(
    './SecretManagement.LiteDB.Extension/SecretManagement.LiteDB.Extension.psd1'
  )
  # Make this module dependant on the SecretManagement module
  RequiredModules = @(
    "Microsoft.Powershell.SecretManagement"
  )
  # Set the minimum &lt;a href="https://adamtheautomator.com/check-powershell-version/" title="PowerShell version" rel="noopener noreferrer"&gt;PowerShell version&lt;/a&gt; of 7.0 as Core is the only one supported by the primary module and this is the recommended PowerShell version
  PowershellVersion = '7.0'
  FunctionsToExport = @()
  CmdletsToExport   = @()
  VariablesToExport = @()
  AliasesToExport   = @()
  # Not necessary, but recommended to tag your module with SecretManagement to assist PowerShell Gallery users in finding your module
  PrivateData = @{
    PSData = @{
      Tags = @('SecretManagement')
    }
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="h-creating-the-extension-vault-directory-and-manifest"&gt;Creating the Extension Vault Directory and Manifest&lt;/h3&gt;

&lt;p&gt;Once you've created the main extension folder and manifest, next create the extension vault module with its manifest. This module is a nested module inside of the main parent extension module directory.&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Create the extension vault's folder. &lt;/li&gt;&lt;/ol&gt;

&lt;pre&gt;&lt;code&gt;New-Item -Path "$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension" -ItemType Directory&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;2. Create the module manifest. This manifest only requires two attributes; RootModule and FunctionsToExport.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;You'll notice the FunctionsToExport parameter shows the same function names as the SecretManagement module itself. An extension vault's commands "overwrite" the default SecretManagement modules.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;pre&gt;&lt;code&gt;New-ModuleManifest -RootModule '.\SecretManagement.LiteDB.Extension.psm1' -FunctionsToExport @('Set-Secret','Get-Secret','Remove-Secret','Get-SecretInfo','Test-SecretVault') -Path "$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension.psd1"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;3. Once New-ModuleManifest creates the template manifest, open it up and ensure it mirrors the below manifest.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@{
  ModuleVersion     = '0.0.0.1'
  # The command definitions located in the same directory as this module
  RootModule        = '.\SecretManagement.LiteDB.Extension.psm1'
  # Only export the functions that the SecretManagement module expects
  FunctionsToExport = @('Set-Secret','Get-Secret','Remove-Secret','Get-SecretInfo','Test-SecretVault')
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now that our module is all set up, let's implement the functions themselves! As mentioned before, to allow the PowerShell SecretManagement module to interact with your extension vault, you must define functions with the &lt;em&gt;exact&lt;/em&gt; same name as the ones shown below other than the LiteDb-specific function &lt;code&gt;Open-Database&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id="h-create-the-vault-function-scaffolding"&gt;Create the Vault Function "Scaffolding"&lt;/h2&gt;

&lt;p&gt;Since you'll be devouring a lot of code in this article, first build the PSM1 module "scaffolding". This will allow you to insert code into this framework as you go along.&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;Create the extension vault PSM1 module by first creating a text file. &lt;/li&gt;&lt;/ol&gt;

&lt;pre&gt;&lt;code&gt;New-Item -Path "$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1" -ItemType File&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;2. Copy and paste the following "scaffolding" code into the PSM1 file. Code-specific comments are inline.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;You must use the standard function names defined above in the Understanding section to interact with the SecretManagement module. You can also add your own functions to the module as "helper" functions if you'd like as shown in the below PSM1 file (Open-Database).&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;pre&gt;&lt;code&gt;# Necessary to correctly override and implement the existing SecretManagement functions
Using Namespace Microsoft.PowerShell.SecretManagement

# An internal function, specific to this LiteDB vault, that will be used to open the database for interaction
Function Open-Database {}

# The following five functions are the standard SecretManagement functions that we will implement
Function Get-Secret {}

Function Set-Secret {}

Function Remove-Secret {} 

Function Get-SecretInfo {}

Function Test-SecretVault {}&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="h-build-the-functions"&gt;Build the Functions&lt;/h2&gt;

&lt;p&gt;The next step is to add functionality to the extension vault. This functionality means adding functions to the extension vault (module).&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Though we are creating an interface to store secrets, in this tutorial, we are not implementing the database's encryption, and in a production database that would be recommended. Once the database has been encrypted, decryption is done by passing a password upon opening the database for reading or writing.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="h-creating-the-helper-open-database-function"&gt;Creating the "Helper" &lt;code&gt;Open-Database&lt;/code&gt; Function&lt;/h3&gt;

&lt;p&gt;Typically, you'll only be building the standard set of functions but for LiteDB, the tutorial has a "helper" function called &lt;code&gt;Open-Database&lt;/code&gt;. This function is strictly an internal function utilized by the extension module to simplify opening the database and making it available to the exposed functions. Code comments in-line.&lt;/p&gt;

&lt;p&gt;Copy and paste this code into your &lt;em&gt;$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1&lt;/em&gt; module.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Function Open-Database {
  [CmdletBinding()]
  
  Param (
    # The path to the actual database file on disk
    [String]$Path
  )

  Begin {
    # Make the vault parameters from the Register-SecretVault function available
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters
  }

  Process {
        # Look for if the type has already been added. If it has not, attempt to find the DLL path and perform an Add-Type to make the LiteDB class available
    If ( -Not ([System.Management.Automation.PSTypeName]'LiteDB.LiteDatabase').Type ) {
      $standardAssemblyFullPath = (&lt;a href="https://adamtheautomator.com/powershell-get-childitem/" title="Get-ChildItem" rel="noopener noreferrer"&gt;Get-ChildItem&lt;/a&gt; -Filter '*.dll' -Recurse (Split-Path (Get-Package -Name 'LiteDB').Source)).FullName | Where-Object {$_ -Like "*standard*"} | Select-Object -Last 1

      Add-Type -Path $standardAssemblyFullPath -ErrorAction 'SilentlyContinue'
    }

    Write-Verbose "Opening Database: $Path" -Verbose:$VaultParameters.Verbose
        
        # Despite the New method, this will open an existing database or create a new one if that database does not exist on disk
    $Database = [LiteDB.LiteDatabase]::New($Path)

    If ($Database) {
      Write-Verbose "Database Opened: $($Database | Out-String)" -Verbose:$VaultParameters.Verbose

            # Output the database variable for use by consuming functions
      $Database
    } Else {
      Throw "Failed to open Database"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;You may notice that the above snippet is doing something odd, &lt;code&gt;Write-Verbose "Verbose Message" -Verbose:$VaultParameters.Verbose&lt;/code&gt;. If you attempt to display a verbose message by passing in the &lt;code&gt;-Verbose&lt;/code&gt; parameter to the function, these internal messages will not display. This makes troubleshooting difficult. To overcome that, pass an additional boolean parameter to the &lt;code&gt;-Verbose&lt;/code&gt; parameter to force the verbose messaging to show.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h3 id="h-creating-the-get-secret-function"&gt;Creating the Get-Secret Function&lt;/h3&gt;

&lt;p&gt;Now that you have defined the &lt;code&gt;Open-Database&lt;/code&gt; utility function, create the &lt;code&gt;Get-Secret&lt;/code&gt; function. This function will most likely be one of the most used functions. This function queries the data source (LiteDb in this case) and returns the "secret". Any specific code comments are in line.&lt;/p&gt;

&lt;p&gt;Copy and paste this code into your &lt;em&gt;$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1&lt;/em&gt; module.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Function Get-Secret {
  [CmdletBinding()]

  Param (
    [String]    $Name,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

    # Locate a single secret as defined by the name passed in
    $Secret = $Collection.FindOne("`$.Name = '$Name'")
    Write-Verbose "Result: $($Secret | Out-String)" -Verbose:$VaultParameters.Verbose

    If ($Secret) {
      # If a secret exists, return the raw value
      $Secret['Value'].RawValue
    } Else {
            # If no secret is found, return $null which is required by the SecretManagement module
      Return $Null
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="h-creating-the-set-secret-function"&gt;Creating the Set-Secret Function&lt;/h3&gt;

&lt;p&gt;Next, you'll need a function to store data in the extension vault. To do that, you'll need the &lt;code&gt;Set-Secret&lt;/code&gt; function This function will either add or update an existing secret depending on what already exists in the database. Any specific code comments are in line.&lt;/p&gt;

&lt;p&gt;Copy and paste this code into your &lt;em&gt;$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1&lt;/em&gt; module.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Function Set-Secret {
  [CmdletBinding()]

  Param (
    [String]    $Name,
    [Object]    $Secret,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

    # Attempt to locate an existing secret in the database
    $Item = $Collection.FindOne("`$.Name = '$Name'")

    If ($Item) {
      # If a secret exists, set the value of that secret to the new secret value passed in
      $Item['value'] = $Secret

      Write-Verbose "Retrieved Existing Item to Update: $($Item | Out-String)" -Verbose:$VaultParameters.Verbose

      Try {
        # Attempt to update the existing secret with the new secret value
        $Collection.Update($Item)
      } Catch {
        Throw "Unable to update item."
      }

            # SecretManagement requires a boolean value to be returned
      Return $True
    } Else {
      # Since the data stored in the database is of the type BSON (Binary Structured Object Notation), we need a utility mapper that converts the PowerShell objects into BSON
      $BSONMapper = [LiteDB.BSONMapper]::New()

            # Construct the password object
      $PasswordObject = @{
        "Name"  = $Name
        "Value" = $Secret
      }

      Write-Verbose "Creating New Entry: $($PasswordObject | Out-String)" -Verbose:$VaultParameters.Verbose

      Try {
        # Attempt to insert the password object as a new entry
        $Collection.Insert($BSONMapper.ToDocument($PasswordObject))
      } Catch {
        Throw "Unable to insert item."
      }

      Return $True
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="h-creating-the-remove-secret-function"&gt;Creating the Remove-Secret Function&lt;/h3&gt;

&lt;p&gt;You'll need a way to remove secrets from the extension vault. For that, you'll need the &lt;code&gt;Remove-Secret&lt;/code&gt; function. This function identifies an existing entry and removes it via the &lt;code&gt;_id&lt;/code&gt; attribute. Any specific code comments are in line.&lt;/p&gt;

&lt;p&gt;Copy and paste this code into your &lt;em&gt;$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1&lt;/em&gt; module.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Function Remove-Secret {
  [CmdletBinding()]

  Param (
    [String]    $Name,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

        # Attempt to locate an entry to remove
    $Item = $Collection.FindOne("`$.Name = '$Name'")

    If ($Item) {
      Write-Verbose "Removing Item: $($Item | Out-String)" -Verbose:$VaultParameters.Verbose

      Try {
                # Remove the entry using the _id attribute, internal to the LiteDB database
        $Collection.Delete($Item['_id'].RawValue)
      } Catch {
        Throw "Unable to delete item."
      }

            # SecretManagement requires a boolean value to be returned
      Return $True
    } Else {
      Return $False
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="h-creating-the-get-secretinfo-function"&gt;Creating the Get-SecretInfo Function&lt;/h3&gt;

&lt;p&gt;Next up, create the &lt;code&gt;Get-SecretInfo&lt;/code&gt; function. This function is similar to &lt;code&gt;Get-Secret&lt;/code&gt; because it queries the data source but it doesn't return the vault. This function returns various information about the secret such as any metadata associated with the secret. Any specific code comments are in line.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;There appears to be a bug in Preview 4 of the PowerShell SecretManagement module. Although the definition calls for just the &lt;code&gt;Filter&lt;/code&gt; parameter, you cannot make this work without both the &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;Filter&lt;/code&gt; parameter added. The &lt;code&gt;Name&lt;/code&gt; parameter is then all that's used. This will most likely change in the future.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;Copy and paste this code into your &lt;em&gt;$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1&lt;/em&gt; module.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Function Get-SecretInfo {
  [CmdletBinding()]

  Param(
    [String]    $Name,
    [String]    $Filter,
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters

    $Database = Open-Database -Path $VaultParameters.Path
  }

  Process {
    Try {
      # Retrieve the collection (i.e. table) to store the secrets in
      $Collection = $Database.GetCollection($VaultParameters.Collection)
      # Make sure that an index exists on the Name field so that queries work properly
      $Collection.EnsureIndex('Name') | Out-Null

      Write-Verbose "Collection Opened: $($Collection | Out-String)" -Verbose:$VaultParameters.Verbose
    } Catch {
      Throw "Unable to open collection."
    }

    Write-Verbose "Testing Name: $Name" -Verbose:$VaultParameters.Verbose
    If ([String]::IsNullOrEmpty($Name)) {
            # If the Name parameter is empty, return all results from the database
      $Results = $Collection.FindAll()
    } Else {
            # If a name is provided return just a single result
      $Results = $Collection.FindOne("`$.Name = '$Name'")
    }

        # SecretManagement requires an array of specific types to be returned, Microsoft.PowerShell.SecretManagement.SecretInformation
        # The first entry is the name of the secret, not the value
        # The second entry is the type, in this case we are only storing strings
        # The third entry is the name of the vault itself
    $ResultsArray = $Results | ForEach-Object {
      [Microsoft.PowerShell.SecretManagement.SecretInformation]::New(
        $PSItem['Name'].RawValue,
        "String",
        $VaultName
      )
    }
    
        # Return an array, and if no results, return an empty array
    If ($ResultsArray) {
      Return $ResultsArray
    } Else {
      Return @()
    }
  }

  End {
    Write-Verbose "Closing Database" -Verbose:$VaultParameters.Verbose
    $Database.Dispose()
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="h-test-secretvault"&gt;Test-SecretVault&lt;/h3&gt;

&lt;p&gt;The final function is the &lt;code&gt;Test-SecretVault&lt;/code&gt; which verifies that everything is set up correctly with your vault.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;The &lt;code&gt;Test-SecretVault&lt;/code&gt; function can be built to validate whatever you'd like. The Azure Keystore extension vault, for example, verifies your Azure subscription status.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;For the LiteDb example you're building, the &lt;code&gt;Test-SecretVault&lt;/code&gt; function below attempts to see if a database and collection already exist. If so, it returns &lt;code&gt;$true&lt;/code&gt;; if not, it returns &lt;code&gt;$false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Copy and paste this code into your &lt;em&gt;$vaultParentPath\SecretManagement.LiteDb\SecretManagement.LiteDB.Extension\SecretManagement.LiteDB.Extension.psm1&lt;/em&gt; module.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Function Test-SecretVault {
  [CmdletBinding()]

  Param (
    [String]    $VaultName,
    [Hashtable] $AdditionalParameters
  )

  Begin {
    $VaultParameters = (Get-SecretVault -Name $VaultName).VaultParameters
  }

  Process {
        # Verify that there is an existing database and that it can be opened with the requested collection available
    If (Test-Path $VaultParameters.Path) {
      $Database   = Open-Database -Path $VaultParameters.Path
      $Collection = $Database.GetCollection($VaultParameters.Collection)

      If ($Database -And $Collection) {
        Return $True
      } Else {
        Return $False
      }
    }

    Return $False
  }

  End {
    $Database.Dispose()
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="h-using-the-powershell-secretmanagement-extension-module"&gt;Using the PowerShell SecretManagement Extension Module&lt;/h2&gt;

&lt;p&gt;Now that you've built the extension vault, start using it! How?&lt;/p&gt;

&lt;ol&gt;&lt;li&gt;First, ensure you have the SecretManagement module loaded. &lt;/li&gt;&lt;/ol&gt;

&lt;pre&gt;&lt;code&gt;# Import the SecretManagement module to make the commands available
Import-Module -Name 'Microsoft.PowerShell.SecretManagement'&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;2. Register LiteDb as an extension vault with the Register-SecretVault cmdlet. To do so, you'll see all of the common parameters you'll need to pass to the cmdlet.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;The below example is using PowerShell splatting but you can also pass the parameters on the same line if you wish.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;pre&gt;&lt;code&gt;$Params = @{
  # Arbitrary name of the vault which could be anything
  "Name"            = 'LiteDBStore'
  # Load the extension module from this directory, in this example, the current one we are in
  "ModuleName"      = './SecretManagement.LiteDB'
  # Define additional parameters to be available to our extension module
  "VaultParameters" = @{
    # Path to the LiteDB database
    'Path'       = "D:\secrets.db"
    # The name of the collection to store the secrets in, think of this as a table
    'Collection' = 'Secrets'
    # Whether to display the Write-Verbose messages in the code
    'Verbose'    = $False
  }
  # Should this vault be treated as the default vault
  "DefaultVault"    = $True
}

Register-SecretVault @Params&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;3. Run the Get-SecretVault command to ensure the SecretManagement module finds the new vault successfully.&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%2Flrqt7ts6rpl8jnk0zpi7.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%2Fi%2Flrqt7ts6rpl8jnk0zpi7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4. Now that the vault has been registered, run a quick test to make sure all the functions work as expected. If all goes well, you should receive no errors with only nice, beautiful output.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Verify that the vault is correctly configured
Test-SecretVault -Name 'LiteDBStore'
# Create a new secret entry
Set-Secret -Name 'FirstSecret' -Secret 'FirstSecretValue'
# Display all secret entries
Get-SecretInfo
# Get the password of the secret as plaintext, default is a securestring
Get-Secret -Name 'FirstSecret' -AsPlainText
# Remove the secret and verify that it is no longer available
Remove-Secret -Name 'FirstSecret' -Vault 'LiteDBStore'
Get-SecretInfo&lt;/code&gt;&lt;/pre&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%2Fg5c5qek99ps8pd9va9fb.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%2Fi%2Fg5c5qek99ps8pd9va9fb.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;To remove and reload a vault, as you make changes, unregister the vault by running &lt;code&gt;Unregister-SecretVault -Name 'LiteDBStore'&lt;/code&gt;. If you're developing a new extension vault, also be sure to remove the module as you make changes with &lt;code&gt;Remove-Module -Name 'SecretManagement.LiteDB'&lt;/code&gt;. Running &lt;code&gt;Register-SecretVault&lt;/code&gt;again automatically imports your module.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="h-next-steps"&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;Writing an extension vault for PowerShell SecretManagement preview 4 is not difficult once you grasped the structure and the intricacies of the SecretManagement module. This common framework will open up a world of possibilities for scripts to not worry about the backing structure of their credentials.&lt;/p&gt;

&lt;p&gt;An extension module could point to a cloud-hosted keystore, or store secrets via an API in almost any location. The functions make abstracting this backend quick and easy!&lt;/p&gt;

&lt;p&gt;Now find your favorite place to store secrets and see if you can build an extension vault for it using the SecretManagement module!&lt;/p&gt;

</description>
      <category>powershell</category>
    </item>
    <item>
      <title>Getting Started with Azure Video Indexer and PowerShell</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Thu, 14 Jan 2021 19:16:03 +0000</pubDate>
      <link>https://dev.to/adbertram/getting-started-with-azure-video-indexer-and-powershell-3i32</link>
      <guid>https://dev.to/adbertram/getting-started-with-azure-video-indexer-and-powershell-3i32</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If you like this article, be sure to check out &lt;a href="https://adamtheautomator.com" rel="noopener noreferrer"&gt;more Azure articles and other how-to posts on cloud computing, system administration, IT, and DevOps on adamtheautomator.com&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After building my Pluralsight course &lt;a href="https://pluralsight.pxf.io/Eg4MD" rel="noopener noreferrer"&gt;Microsoft Azure Cognitive Services: Custom Text to Speech&lt;/a&gt;, I've been playing around more with Azure's various APIs. I recently found myself using the Azure Video Indexer and decided to begin poking at it with PowerShell. Here's a brief rundown.&lt;/p&gt;

&lt;p&gt;To get started, you either first have to &lt;a href="https://vi.microsoft.com/en-us/" rel="noopener noreferrer"&gt;sign up for a trial account&lt;/a&gt; which includes 2,400 minutes of free indexed usage via the API or &lt;a href="https://docs.microsoft.com/en-us/azure/media-services/video-indexer/connect-to-azure" rel="noopener noreferrer"&gt;create a Video Indexer account&lt;/a&gt; in your Azure subscription. For now, I decided to just use the free trial since I was just testing it out.&lt;/p&gt;

&lt;h2 id="subscribing-to-the-authorization-product-api"&gt;Subscribing to the Authorization Product API&lt;/h2&gt;

&lt;p&gt;The first task is authenticating to the Azure Video Indexer. To do this, you first need to subscribe to the Authorization Product API. You'll do that by logging into the Video Indexer developer portal, clicking on the &lt;a href="https://api-portal.videoindexer.ai/products/authorization" rel="noopener noreferrer"&gt;Products section&lt;/a&gt; and clicking &lt;em&gt;Subscribe&lt;/em&gt;. For a full rundown, check out the Microsoft tutorial &lt;a href="https://docs.microsoft.com/en-us/azure/media-services/video-indexer/video-indexer-use-apis#subscribe-to-the-api" rel="noopener noreferrer"&gt;Use the Video Indexer API&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="finding-your-account-id"&gt;Finding Your Account ID&lt;/h2&gt;

&lt;p&gt;Once you've subscribed to the Authorization Product, you'll then need your account ID. There are a few ways to get this but the easiest is by heading to your &lt;a href="https://www.videoindexer.ai/settings/account" rel="noopener noreferrer"&gt;Azure Video Indexer service page&lt;/a&gt;. If this link doesn't bring you directly to your &lt;em&gt;Account&lt;/em&gt; section on the &lt;em&gt;Settings&lt;/em&gt; page, click on your user profile in the upper-right corner of the page and choose &lt;em&gt;Settings&lt;/em&gt; as shown below then once there, click on &lt;em&gt;Account&lt;/em&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%2Focl8nsd6a4f8fevxkpkf.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%2Fi%2Focl8nsd6a4f8fevxkpkf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once on the &lt;em&gt;Account&lt;/em&gt; section, copy your &lt;em&gt;Account ID&lt;/em&gt; as shown below.&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%2F050iv1zprclqlk7atuf6.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%2Fi%2F050iv1zprclqlk7atuf6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2 id="finding-your-api-key"&gt;Finding Your API Key&lt;/h2&gt;

&lt;p&gt;Next, you'll need to find your API key. Your API key can be found in on your &lt;a href="https://api-portal.videoindexer.ai/products/authorization" rel="noopener noreferrer"&gt;Authorization product subscription in the Video Indexer Developer Portal&lt;/a&gt;. If this link doesn't bring you directly there, navigate to the &lt;a href="https://api-portal.videoindexer.ai/" rel="noopener noreferrer"&gt;Video Indexer Developer Portal home page&lt;/a&gt;, click on Products --&amp;gt; Authorization and then on the Authorization product you've already subscribed to as shown below.&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%2Fzt8m7whkeelyo79azoza.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%2Fi%2Fzt8m7whkeelyo79azoza.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once in the Authorization product, you should see your primary and secondary key. You just need your primary one for now.&lt;/p&gt;

&lt;p&gt;Click on &lt;em&gt;Show&lt;/em&gt; next to your primary key to see your personal API key then copy the key to your clipboard.&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%2Fr5i0biup0f5qu21gadik.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%2Fi%2Fr5i0biup0f5qu21gadik.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="setting-up-powershell-variables"&gt;Setting up PowerShell Variables&lt;/h2&gt;

&lt;p&gt;Let's now start building the PowerShell script. I like to define values in variables that I'll be using throughout my script. I'll do that now. Notice that I'm also using TLS1.2. This is required.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$apiUrl = 'https://api.videoindexer.ai'
$apiKey = 'XXXXXX'
$accountId = 'XXXXXXXX'
$location = 'trial'

## eastus2, westus2, etc
$accountUrl = "$apiUrl/$location/Accounts/$accountId"&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="getting-an-access-token"&gt;Getting an Access Token&lt;/h2&gt;

&lt;p&gt;By now, you should have two critical pieces of information; your account ID and your API key. If so, you can now get an access token. The endpoint URI to grab an access token is required in the form of:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;https://api.videoindexer.ai/auth/&amp;lt;Location&amp;gt;/Accounts/&amp;lt;AccountID&amp;gt;/AccessToken&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You already have your account ID but the location can be tricky to find. Since we're using a trial key, the location is &lt;code&gt;trial&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, the authentication URI has an optional query parameter called &lt;code&gt;allowEdit&lt;/code&gt;. This query parameter defines if the token you're getting here will be able to modify resources when it's used. That means if you'll be able to use HTTP methods like POST and PUT rather than just GET requests. In this article, we will be uploading a video, so we need to have this capability with the access token. The authentication URI then ends up looking like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$tokenUri = "https://api.videoindexer.ai/auth/$location/Accounts/$accountId/AccessToken?allowEdit=True"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You'll need to pass the API key to the access token request via headers using the &lt;code&gt;Ocp-Apim-Subscription-Key&lt;/code&gt; key. Below you'll see the way you can run the &lt;code&gt;Invoke-RestMethod&lt;/code&gt; command to make the API call. Once this runs, you should have a shiny new access token stored in the &lt;code&gt;$token&lt;/code&gt; variable.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$headers = @{
    'Ocp-Apim-Subscription-Key' = $apiKey
}

$params = @{
    'Uri' = $tokenUri
    'Headers' = $headers
    'Method'  = 'GET'
}

$token = Invoke-RestMethod @params&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="uploading-a-video"&gt;Uploading a Video&lt;/h2&gt;

&lt;p&gt;Once you've got a token, let's upload a sample video to test it out. You can upload a video two ways either via uploading a video on your own or using a URL. I'll use a URL. To do that, you'll use the Videos API passing the name of the video that will be displayed in the Video Indexer console, the &lt;code&gt;videoUrl&lt;/code&gt; query parameter specifying the URL to the video to upload and since my video is in English, I'm using the &lt;code&gt;language&lt;/code&gt; parameter although is not required.&lt;/p&gt;

&lt;p&gt;Also notice below that the URL must be URL-encoded so I'm using the &lt;code&gt;UrlEncode()&lt;/code&gt; method to make that happen then passing that URL to the &lt;code&gt;Invoke-RestMethod&lt;/code&gt; command.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$videoName = 'testing'
$videoUrl = ''
$encUrl = [System.Web.HttpUtility]::UrlEncode($videoUrl)
$uploadVideoUrl = "$accountUrl/Videos?name=$videoName&amp;amp;accessToken=$token&amp;amp;videoUrl=$encUrl&amp;amp;language=en-US"  

$params = @{
    'Uri' = $uploadVideoUrl
    'Method' = 'POST'
}

$uploadedVideo = Invoke-RestMethod @params&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="wait-for-indexing"&gt;Wait for Indexing&lt;/h2&gt;

&lt;p&gt;This will upload the video to Azure Video Indexer and indexing will immediately start. You could stop here but there are so many other things to do! Just for fun, let's download the transcription that's created. Before we can do that, we'll need to wait for the transcription to finish.&lt;/p&gt;

&lt;p&gt;To wait for the indexing to complete, we'll make another API call to the Videos endpoint and this time query the &lt;a href="https://api-portal.videoindexer.ai/docs/services/Operations/operations/Get-Video-Index" rel="noopener noreferrer"&gt;Index&lt;/a&gt; path of the video we just uploaded. Since API result was captured in the &lt;code&gt;$uploadedVideo&lt;/code&gt; variable, we can reference the &lt;code&gt;id&lt;/code&gt; property building the correct URI.&lt;/p&gt;

&lt;p&gt;We'll then build a &lt;code&gt;while&lt;/code&gt; loop which continually checks the &lt;code&gt;state&lt;/code&gt; property. When the &lt;code&gt;state&lt;/code&gt; property value is no longer &lt;code&gt;Uploaded&lt;/code&gt; or &lt;code&gt;Processing&lt;/code&gt;, it's done!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$videoUri = "$accountUrl/Videos/$($uploadedVideo.id)/Index?accessToken=$token"

while ((Invoke-RestMethod -Uri $videoUri -Method 'GET').state -in @('Uploaded', 'Processing')) {
    Write-Host "Waiting for video to index..."
    &lt;a href="https://adamtheautomator.com/start-sleep-powershell/" title="Start-Sleep" rel="noopener noreferrer"&gt;Start-Sleep&lt;/a&gt; -Seconds 5
}&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="getting-a-video-transcript"&gt;Getting a Video Transcript&lt;/h2&gt;

&lt;p&gt;At this point, we can do just about anything but I personally needed to pull video transcripts. I create a small code snippet to make that happen using the &lt;a href="https://api-portal.videoindexer.ai/docs/services/Operations/operations/Get-Video-Captions" rel="noopener noreferrer"&gt;Captions endpoint&lt;/a&gt; as shown below.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;## Allowed values: Vtt/Ttml/Srt/Txt/Csv
$captionUri = "$accountUrl/Videos/$($uploadedVideo.id)/Captions?accessToken=$token&amp;amp;format=Txt"
$captions = Invoke-RestMethod -Uri $captionUri -Method 'GET'&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;

&lt;p&gt;The code covered in this article works but it's not useful on it's own if you intend to use Azure Video Indexer in production or even seriously in a test environment. I recommend building a &lt;a href="https://adamtheautomator.com/powershell-modules/" title="PowerShell module" rel="noopener noreferrer"&gt;PowerShell module&lt;/a&gt; which gives you the ability to compartmentalize a lot of this code into functions.&lt;/p&gt;

&lt;p&gt;For an example, check out my &lt;a href="https://github.com/adbertram/AzTextToSpeech/tree/master" rel="noopener noreferrer"&gt;AzTextToSpeech module&lt;/a&gt; for inspiration. It'd require quite a bit of modification but does have some similarities.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>powershell</category>
      <category>videoindexer</category>
    </item>
    <item>
      <title>Running PowerShell Scripts in Azure DevOps Pipelines (2 of 2)</title>
      <dc:creator>Adam the Automator</dc:creator>
      <pubDate>Wed, 19 Feb 2020 14:00:00 +0000</pubDate>
      <link>https://dev.to/adbertram/running-powershell-scripts-in-azure-devops-pipelines-2-of-2-3j0e</link>
      <guid>https://dev.to/adbertram/running-powershell-scripts-in-azure-devops-pipelines-2-of-2-3j0e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally posted on the &lt;a href="https://adamtheautomator.com"&gt;Adam the Automator blog&lt;/a&gt;. If you enjoy this article, be sure to come and check out this and hundreds of other sysadmin, cloud and DevOps posts!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No one product will ever provide all of the built-in tools you need to get the job done. Scripts are like the glue that brings workflows together and the shims that make solutions work. in Azure DevOps (AzDo) Pipelines, PowerShell and Bash scripts are your best friends.&lt;/p&gt;

&lt;p&gt;But how do you invoke scripts in an Azure Pipeline? What are all of the little gotchas and ways to best run scripts in a pipeline? You'll find out in this article.&lt;/p&gt;

&lt;p&gt;In this article, you're going to get hands-on with scripts in pipelines. You'll learn to do &lt;em&gt;all&lt;/em&gt; the things with both PowerShell and Bash scripts in pipelines. If you're just joining us and want to learn a little more backstory on running scripts in pipelines, be sure to check out the &lt;a href="https://adamtheautomator.com/azure-devops-pipelines-powershell-bash"&gt;first article&lt;/a&gt; in this two-article series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before You Start
&lt;/h2&gt;

&lt;p&gt;This article will be a combination of teaching and hands-on tutorial. If you intend to try out any of the examples for yourself, be sure to have a few prerequisites set up ahead of time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An existing AzDo pipeline created linked to a repo - Learn how to create a pipeline &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/create-first-pipeline"&gt;via the web interface&lt;/a&gt; or using the Az CLI in &lt;a href="https://adamtheautomator.com/azure-devops-pipeline-infrastructure/#creating-the-pipeline"&gt;this Azure Pipelines article&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What You're Going to Learn
&lt;/h2&gt;

&lt;p&gt;In this hands-on tutorial, you're going to learn everything there is to know about running PowerShell and Bash scripts in AzDo Pipelines. Using the PowerShell and Bash tasks, you'll see how to invoke scripts, pass parameters to them, control errors and how to fail a task in the pipeline should a problem arise in the script.&lt;/p&gt;

&lt;p&gt;You're also going to learn how to use AzDo pipeline variables in scripts and also how to set them using &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands"&gt;AzDo logging commands&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PowerShell Task is Your Friend
&lt;/h2&gt;

&lt;p&gt;To run a PowerShell script in a pipeline requires using the &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/powershell"&gt;PowerShell task&lt;/a&gt;. The PowerShell task takes a script or PowerShell code from the pipeline and runs it on a pipeline agent. Depending on the options chosen, the pipeline agent will either be on Windows or Linux.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can also use more specific use case tasks like the &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/azure-powershell"&gt;Azure PowerShell task&lt;/a&gt; too but those won't be covered here.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  PowerShell Task Schema
&lt;/h2&gt;

&lt;p&gt;The PowerShell task is called &lt;code&gt;PowerShell@2&lt;/code&gt; and has a schema that looks like below. You can see you've got a few options at your disposal for running scripts under the &lt;code&gt;inputs&lt;/code&gt; section.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
  inputs:
    targetType:
    filePath: 
    arguments:
    script:
    errorActionPreference:
    failOnStderr: true
    ignoreLASTEXITCODE: true
    pwsh: true
    workingDirectory: 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Each attribute you decide to use in &lt;code&gt;inputs&lt;/code&gt; affects the behavior of how the PowerShell code/script runs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
    &lt;tr&gt;
        &lt;td&gt;Name&lt;/td&gt;
        &lt;td&gt;Mandatory&lt;/td&gt;
        &lt;td&gt;Options&lt;/td&gt;
        &lt;td&gt;Used with&lt;/td&gt;
        &lt;td&gt;Default Value&lt;/td&gt;
        &lt;td&gt;Description&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;targetType&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;filePath, inline&lt;/td&gt;
        &lt;td&gt;All&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;The PowerShell task allows you to add PowerShell code directly within the YAML pipeline or execute an existing script in the source repo. Here you can specify either filePath providing the path to the script to run or use inline which indicates that you'll be adding the PowerShell code directly int the YAML pipeline.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;filePath&lt;/td&gt;
        &lt;td&gt;Yes&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;targetType: filePath&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;This attribute is where you specify the path of the script to execute. This path typically points to a script in your source repo that the pipeline checks out when it runs. For example, to tell the PowerShell task to execute a script called script.ps1 in the root of the source repo, you'd use a predefined variable like $(System.DefaultWorkingDirectory)\script.ps1.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;arguments&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;targetType: filePath&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;If you've provided a script via the filePath attribute and that script is built with parameters, this is where you would pass in values to those parameters. Note that when using inline code, this option is not used. You should specify named parameters like `-Name someName -Path -Value "some value"`&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;script&lt;/td&gt;
        &lt;td&gt;Yes&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;inline&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;Enclosed in quotes, this is where you provide the PowerShell code to execute.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;errorActionPreference&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;SilentlyContinue, Continue, Inquire, Stop&lt;/td&gt;
        &lt;td&gt;All&lt;/td&gt;
        &lt;td&gt;Stop&lt;/td&gt;
        &lt;td&gt;Use this to set $ErrorActionPreference in the script if you haven't done so already.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;failOnStderr&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;true&lt;/td&gt;
        &lt;td&gt;All&lt;/td&gt;
        &lt;td&gt;false&lt;/td&gt;
        &lt;td&gt;Setting this value to true will fail the PowerShell task in the pipeline is an error is thrown via PowerShell. Otherwise, the task will only fail if the scripts exits with a non-zero exit code.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;ignoreLASTEXITCODE&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;true&lt;/td&gt;
        &lt;td&gt;All&lt;/td&gt;
        &lt;td&gt;false&lt;/td&gt;
        &lt;td&gt;If set to false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of the script. This will cause the last exit code from an external command to be propagated as the exit code of PowerShell.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;pwsh&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;true&lt;/td&gt;
        &lt;td&gt;All&lt;/td&gt;
        &lt;td&gt;false&lt;/td&gt;
        &lt;td&gt;If the pipeline agent is running on Windows, this will force the code/script to be executed using pwsh.exe (PowerShell Core). If not set to true, the task will default to Windows PowerShell on Windows pipeline agents.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;workingDirectory&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;All&lt;/td&gt;
        &lt;td&gt;$(Build.SourcesDirectory)&lt;/td&gt;
        &lt;td&gt;The working directory to execute the script in. This affects paths in the script like _.\command.ps1_.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;env&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;All&lt;/td&gt;
        &lt;td&gt;&lt;/td&gt;
        &lt;td&gt;A list of additional items to map into the process's environment. By default, pipeline variables are mapped but secret variables are not. Here is where you would specify them like `MySecret: $(Foo)`.&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now that you have an idea of what's possible, let's dive into each attribute and see what's possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Simple One-Liners
&lt;/h2&gt;

&lt;p&gt;In the PowerShell task's most simplest form, you can run a single line of PowerShell using a &lt;code&gt;targetType&lt;/code&gt; of &lt;code&gt;inline&lt;/code&gt; and by specifying the code to run via the &lt;code&gt;script&lt;/code&gt; attribute as shown below.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
    inputs:
            targetType: 'inline'
      script: 'Write-Host "This is me running PowerShell code!"'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When the pipeline is run, you'll then see the output shown in the log.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4pi-ORnS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-31.png%3Fezimgfmt%3Drs:705x339/rscb11/ng:webp/ngcb11" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4pi-ORnS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-31.png%3Fezimgfmt%3Drs:705x339/rscb11/ng:webp/ngcb11" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to take a shortcut to this approach, you can save yourself a few lines but using the optional &lt;code&gt;powershell&lt;/code&gt; shortcut task too.&lt;/p&gt;

&lt;p&gt;To perform the exact same function as above, you can also simply use the &lt;code&gt;powershell&lt;/code&gt; term followed by the code to run as shown below.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- powershell: 'Write-Host "This is me running PowerShell code!"'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;If you'd like to learn more about running PowerShell code inline, check out this the &lt;a href="https://adamtheautomator.com/azure-devops-pipelines-powershell/#inline-code-vs-scripts"&gt;Inline&lt;/a&gt; &lt;a href="https://adamtheautomator.com/p/0c9641ce-899d-4158-93d8-c75b6f45fa85/#inline-code-vs-scripts"&gt;Code vs. Scripts section&lt;/a&gt; of the first article in this series.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sidenote: Windows PowerShell vs. PowerShell (Core)
&lt;/h2&gt;

&lt;p&gt;Before you get too much farther, it's important to point out some behavioral differences in Windows PowerShell vs. PowerShell (Core) and how to control what version of PowerShell your scripts run on.&lt;/p&gt;

&lt;p&gt;In the example above, the version of PowerShell that the code executed on completely depended on the pipeline agent the code was running on. The pipeline is smart enough to handle this for you but you will get caught by this at some point.&lt;/p&gt;

&lt;p&gt;For example, what if you have a Windows PowerShell-specific code and use the &lt;code&gt;powershell&lt;/code&gt; task assuming that it will run on Windows? It might but what if you've got a big pipeline defined and you forgot you added a &lt;code&gt;pool: ubunbu-latest&lt;/code&gt; line for that job? The task will still run on Linux but it has no choice but to run PowerShell (Core).&lt;/p&gt;

&lt;p&gt;Using the task above as an example, let's now say you've specifically defined the pipeline agent to run on Linux like below. The tasks are exactly the same but the pipeline agent is not.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pool:
  vmImage: "ubuntu-latest"

steps:
  - task: PowerShell@2
    inputs:
      targetType: 'inline'
      script: 'Write-Host "This is me running PowerShell code!"'
    - powershell: 'Write-Host "This is me running PowerShell code!"'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You'll then see in the job log, the pipeline automatically chose &lt;em&gt;pwsh&lt;/em&gt;. You'll see the same in Windows where the pipeline executes &lt;em&gt;powershell.exe&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yJzhfWPv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-32.png%3Fezimgfmt%3Drs:850x63/rscb11/ng:webp/ngcb11" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yJzhfWPv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-32.png%3Fezimgfmt%3Drs:850x63/rscb11/ng:webp/ngcb11" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Be Explicit about PowerShell Versions
&lt;/h4&gt;

&lt;p&gt;If there might ever be a possibility that you're running code that depends on a specific version of PowerShell, always be explicit about the version you'd like to run on.&lt;/p&gt;

&lt;p&gt;To run PowerShell (Core), always use the &lt;code&gt;pwsh: true&lt;/code&gt; attribute on the &lt;code&gt;PowerShell@2&lt;/code&gt; task or the &lt;code&gt;pwsh&lt;/code&gt; shortcut task. Don't assume that the pipeline will pick the right version for you. You'll probably never remember you made that "quick change to troubleshoot a thing" by changing the pipeline agent before it's too late.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
    inputs:
            targetType: 'inline'
      script: 'Write-Host "This is me running PowerShell code!"'
            pwsh: true

- pwsh: 'Write-Host "This is me running PowerShell code!"'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Running Scripts
&lt;/h2&gt;

&lt;p&gt;If you need to run some PowerShell code longer than a few lines or need to pass parameters to your code, you'll need to step up to executing scripts. Using the PowerShell task, you can do this by setting the &lt;code&gt;targetType&lt;/code&gt; to &lt;code&gt;filePath&lt;/code&gt; and then specifying the path of the script to run via the &lt;code&gt;filePath&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;For example, perhaps you have a PowerShell script called &lt;em&gt;script_no_params.ps1&lt;/em&gt; in the root of your source repo. Below you can see an example of calling the &lt;em&gt;script.ps1&lt;/em&gt; script located in the &lt;code&gt;System.DefaultWorkingDirectory&lt;/code&gt; pipeline variable path. This is the directory where the source repo files are downloaded to when the pipeline runs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
    inputs:
            targetType: 'filePath'
      filePath: '$(System.DefaultWorkingDirectory)\script_no_params.ps1'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Watch out for forward and backslash inconsistencies! If you're running a script on Linux, use forward slashes. On Windows, use backslashes when specifying the &lt;code&gt;filePath&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The script contains a single line.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[CmdletBinding()]
param()

Write-Host "I'm running in a PowerShell script!!"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When the pipeline is run, you'll see that the pipeline reads the code inside of the script, creates it's own PowerShell script and then executes the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zM3cmxOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-33.png%3Fezimgfmt%3Drs:850x59/rscb11/ng:webp/ngcb11" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zM3cmxOH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-33.png%3Fezimgfmt%3Drs:850x59/rscb11/ng:webp/ngcb11" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you intend to run an existing PowerShell script, be sure you don't have the &lt;code&gt;- checkout: none&lt;/code&gt; line in your pipeline. Otherwise, the script will never be downloaded to the agent.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing Parameters
&lt;/h3&gt;

&lt;p&gt;If you have a script that has one or more parameters, you can pass parameters to those scripts using the &lt;code&gt;arguments&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that you cannot pass parameters to inline code using the &lt;code&gt;arguments&lt;/code&gt; attribute.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;arguments&lt;/code&gt; attribute accepts parameters the exact same way you'd specify a named parameter within PowerShell itself using &lt;code&gt;-[parameter_name] [parameter_value]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, perhaps you have a script called &lt;em&gt;script.ps1&lt;/em&gt; in the root of your source repo. That script contains two parameters called &lt;code&gt;$foo&lt;/code&gt; and &lt;code&gt;$bar&lt;/code&gt; as shown below.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[CmdletBinding()]
param(
  $foo,
  $bar
)

Write-Host "Value of foo is $foo and value of bar is $bar"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can provide values to the &lt;code&gt;$foo&lt;/code&gt; and &lt;code&gt;$bar&lt;/code&gt; parameters via the &lt;code&gt;arguments&lt;/code&gt; attribute in the YAML pipeline like below.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
    inputs:
            targetType: 'filePath'
      filePath: '$(System.DefaultWorkingDirectory)/script.ps1'
            arguments: "-foo 'foovalue' -bar 'barvalue'"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can then see the values were passed to the script in the job output log.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iT9BC9yv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-34.png%3Fezimgfmt%3Drs:850x70/rscb11/ng:webp/ngcb11" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iT9BC9yv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-34.png%3Fezimgfmt%3Drs:850x70/rscb11/ng:webp/ngcb11" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handing Errors and Warnings
&lt;/h2&gt;

&lt;p&gt;When a PowerShell is invoked via a pipeline and returns an error or warning, the pipeline behavior greatly depends on how you configure it. A PowerShell script can "error out" in a few different ways such as soft-terminating, hard-terminating errors and exiting with a non-zero exit code. Also, don't forget about that warning stream!&lt;/p&gt;

&lt;h3&gt;
  
  
  Errors
&lt;/h3&gt;

&lt;p&gt;When the PowerShell task encounters an error, it may or may not fail the task in the pipeline. If you have a script that may return an error but it's not serious enough to fail the entire pipeline task, you can manage this behavior with the &lt;code&gt;errorActionPreference&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;By default, the pipeline sets all PowerShell scripts to an &lt;code&gt;$ErrorActionPreference&lt;/code&gt; value to &lt;code&gt;Stop&lt;/code&gt;. This means that all soft and hard-terminating errors will force PowerShell to return a non-zero exit code thus failing the pipeline task.&lt;/p&gt;

&lt;p&gt;To demonstrate, perhaps you have a script that returns a soft-terminating error like &lt;code&gt;Write-Error&lt;/code&gt; does or a hard-terminating error like &lt;code&gt;throw&lt;/code&gt; does.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Soft
Write-Error -Message 'This is a soft-terminating error'

## Hard
throw 'This is a hard-terminating error'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When the pipeline encounters this script, the task will fail because PowerShell didn't return a zero exit code as you can see below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9AxLBL_M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-35.png%3Fezimgfmt%3Drs:688x168/rscb11/ng:webp/ngcb11" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9AxLBL_M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-35.png%3Fezimgfmt%3Drs:688x168/rscb11/ng:webp/ngcb11" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although not recommended, if you'd like the script to fail but not fail the pipeline task, you can do so by setting the &lt;code&gt;errorActionPreference&lt;/code&gt; attribute to &lt;code&gt;SilentyContinue&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
    inputs:
            targetType: 'filePath'
      filePath: '$(System.DefaultWorkingDirectory)/script.ps1'
            errorActionPreference: SilentlyContinue
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Modifying Exit Code Behavior
&lt;/h3&gt;

&lt;p&gt;By default, the PowerShell task fails if PowerShell returns a non-zero exit code. You saw an example of this above. However, if you need to manipulate that behavior, you can do so using the &lt;code&gt;ignoreLASTEXITCODE&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Whenever a PowerShell script turns, it always populates &lt;a href="https://techibee.com/powershell/what-is-lastexitcode-and-in-powershell/1847"&gt;a variable called &lt;code&gt;$LASTEXITCODE&lt;/code&gt;&lt;/a&gt;. This exit code, coincidentally, returns the last exit code the PowerShell script returned. This is what the pipeline task reads to indicate success or failure.&lt;/p&gt;

&lt;p&gt;Perhaps you have a situation where a command you're running inside of a script returns a non-zero exit code but you &lt;em&gt;know&lt;/em&gt; it was successful anyway. Don't get me started on software installers! If you'd like the pipeline task to succeed, you can force your own exit code.&lt;/p&gt;

&lt;p&gt;Let's say you have a script called &lt;em&gt;script.ps1&lt;/em&gt; like below that modifies the exit code the PowerShell scripts quits with. In the example below, maybe the command returns a zero exit code which typically indicates success but you know that's actually a failure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$output = ./some-command.exe
if ($LASTEXITCODE -eq 0) {
    exit 1
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you'd run this task without using the &lt;code&gt;ignoreLastExitCode&lt;/code&gt; attribute, you'd find the task still shows success. Why? Because the task doesn't care what exit code PowerShell &lt;em&gt;actually&lt;/em&gt; returns. It uses the value of &lt;code&gt;$LASTEXITCODE&lt;/code&gt; to determine that.&lt;/p&gt;

&lt;p&gt;To remove the dependency on the &lt;code&gt;$LASTEXITCODE&lt;/code&gt; variable, use the &lt;code&gt;ignoreLastExitCode&lt;/code&gt; attribute as shown below. &lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
    inputs:
            targetType: 'filePath'
      filePath: '$(System.DefaultWorkingDirectory)/script.ps1'
            ignoreLastExitCode: true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Showing Custom Errors and Warnings in Job Logs
&lt;/h3&gt;

&lt;p&gt;Although less frequently used, you can also use logging commands to write warnings and errors into the job log using PowerShell. Below is an example. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: PowerShell@2
    inputs:
            targetType: 'inline'
      script: |
                Write-Host "##vso[task.LogIssue type=warning;]This is the warning"
                Write-Host "##vso[task.LogIssue type=error;]This is the error"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fNx-_Xsw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-36.png%3Fezimgfmt%3Drs:850x357/rscb11/ng:webp/ngcb11" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fNx-_Xsw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-36.png%3Fezimgfmt%3Drs:850x357/rscb11/ng:webp/ngcb11" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Adding warnings and errors directly into the job log doesn't effect the success/failure status of the task itself. This feature is useful for logging information to the job log.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Pipeline Variables
&lt;/h2&gt;

&lt;p&gt;The final topic you're going to learn is managing pipeline variables. AzDo makes it easy to set and reference pipeline variables in PowerShell scripts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We're not going to go deep with variables in this section. If you'd like to learn more about pipeline variables, be sure to check out &lt;a href="https://adamtheautomator.com/azure-devops-variables-complete-guide/"&gt;Understanding Azure DevOps Variables [Complete Guide]&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading Pipeline Variables
&lt;/h3&gt;

&lt;p&gt;When you've defined variables in the pipeline, you can read the values of those variables in PowerShell scripts using environment variables.&lt;/p&gt;

&lt;p&gt;Perhaps you've declared a variable in a pipeline like below. The &lt;code&gt;project_name&lt;/code&gt; variable is now available throughout the YAML pipeline.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variables:
    - name: project_name
        value: "foo"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To reference this variable's value in a script, simply reference the same name but as an environment variable as shown below. That's all there is to it. All pipeline variables will always be mapped to environment variables in the pipeline agents.&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write-Host "The value of project_name is $env:project_name"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Setting Pipeline Variables
&lt;/h3&gt;

&lt;p&gt;Perhaps you need to set a pipeline variable in a PowerShell script. Although not quite as intuitive, you can do so using logging commands. Logging commands are how the pipeline talks to the agent. By writing a specifically-crafted string to the "console", you can define variables as shown below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Creates a standard pipeline variable called foo and sets the value to bar
Write-Host "##vso[task.setvariable variable=foo;]bar"

## Creates an output variable called foo and sets the value to bar
Write-Host "##vso[task.setvariable variable=foo;isOutput=true]bar"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Things don't always go the way you'd like so it's important to know a few tips to troubleshoot your way out of a jam.&lt;/p&gt;
&lt;h3&gt;
  
  
  Watch Your Quotes!
&lt;/h3&gt;

&lt;p&gt;If you're running inline code, quotes will apply two places - in the YAML pipeline and in PowerShell. It's easy to place a quote in the wrong spot making the pipeline think the quote is for it and the other way around.&lt;/p&gt;

&lt;p&gt;One of the easiest ways to prevent this is by using multi-line inline code. Instead of invoking PowerShell code in a single line with quotes and having to keep something like this straight:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;powershell: '"This is some code."'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Instead, you can remove the required quotes from the pipeline all together by using a pipe (&lt;code&gt;|&lt;/code&gt;) symbol and adding the code below it. This way removes some complexity.&lt;/p&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;powershell: |&lt;br&gt;
    "This is some code"&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Debugging&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;If you'd like to dig deeper into what the pipeline agent is doing in the background, you can debug pipelines using the &lt;code&gt;system.debug&lt;/code&gt; variable. If you set the &lt;code&gt;system.debug&lt;/code&gt; variable to &lt;code&gt;true&lt;/code&gt; in a pipeline, you'll see a much more verbose output in the job log as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t4D5Z_e2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-37.png%3Fezimgfmt%3Drs:850x578/rscb11/ng:webp/ngcb11" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t4D5Z_e2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://adamtheautomator.com/content/images/2020/02/image-37.png%3Fezimgfmt%3Drs:850x578/rscb11/ng:webp/ngcb11" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enumerating Environment Variables
&lt;/h3&gt;

&lt;p&gt;When working with complex YAML pipelines, you'll probably come across a situation where you need to see what PowerShell is seeing as values for one or more pipeline variables.&lt;/p&gt;

&lt;p&gt;Since the pipeline maps all pipeline variables to environment variables, you can list all of the current environment variables including their values using &lt;code&gt;Get-ChildItem&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;PowerShell stores all environment variables in a PS Drive called &lt;em&gt;Env&lt;/em&gt;. You can then read all environment variables by listing out the contents of this PS drive as shown below.&lt;/p&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get-ChildItem -Path Env:\&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Summary&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;In this article, you went deep with AzDo pipelines and PowerShell. You learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to invoke PowerShell code without writing scripts&lt;/li&gt;
&lt;li&gt;How to invoke existing PowerShell scripts&lt;/li&gt;
&lt;li&gt;Be aware of the differences in Windows PowerShell and PowerShell (Core)&lt;/li&gt;
&lt;li&gt;How to control a success/failure of a pipeline task based off of PowerShell errors and exit codes&lt;/li&gt;
&lt;li&gt;How to write warnings and errors to the pipeline's job log&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now get out there, apply this knowledge and make some awesome AzDo pipelines!&lt;/p&gt;

</description>
      <category>azuredevops</category>
      <category>powershell</category>
    </item>
  </channel>
</rss>
