<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://blog.danskingdom.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.danskingdom.com/" rel="alternate" type="text/html" /><updated>2026-03-06T16:14:34+00:00</updated><id>https://blog.danskingdom.com/feed.xml</id><title type="html">Daniel Schroeder’s Programming Blog</title><subtitle>Daniel Schroeder&apos;s Programming Blog - Sharing my tips, tricks, and code to help other developers be more productive. https://blog.danskingdom.com</subtitle><author><name>Daniel Schroeder</name></author><entry><title type="html">Easily capture terminal output from users you help remotely</title><link href="https://blog.danskingdom.com/Easily-capture-terminal-output-from-users-you-help-remotely/" rel="alternate" type="text/html" title="Easily capture terminal output from users you help remotely" /><published>2026-03-01T00:00:00+00:00</published><updated>2026-03-01T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Easily-capture-terminal-output-from-users-you-help-remotely</id><content type="html" xml:base="https://blog.danskingdom.com/Easily-capture-terminal-output-from-users-you-help-remotely/"><![CDATA[<p>When helping others troubleshoot issues with their PC, you often need to see the output of commands they run.
The easiest way to do this is to synchronously video call and screen share, but that’s not always possible.</p>

<p>Below are some alternative asynchronous options for seeing the output of commands they run.</p>

<h2 id="print-screen">Print Screen</h2>

<p>An easy option is to use the <kbd>PrtSc</kbd> (or <kbd>Print Screen</kbd>) key to capture the whole screen, or <kbd>Alt</kbd> + <kbd>PrtSc</kbd> to capture just the active window.</p>

<p>This will copy the screenshot to their clipboard, which they can then paste into a Slack or Teams message or email.</p>

<p>A screenshot is nice and easy, unless the entire output does not fit nicely in a single image, or if you want to be able to copy and paste the output yourself.</p>

<h2 id="terminal-command-options">Terminal command options</h2>

<p>To get the output of a command, you can ask them to run the command and copy the output to the clipboard.</p>

<p>Manually copying the output is not always easy though, as many terminals use block selection which is unintuitive and makes it hard to copy the entire output, especially if the output is long and requires scrolling.</p>

<p><img src="/assets/Posts/2026-03-01-Easily-capture-terminal-output-from-users-you-help-remotely/block-selection-screenshot.png" alt="Example of block selection" /></p>

<p>Instead, we can simply adjust the command they are running to automatically copy the output to the clipboard or save it to a file, which they can then send to you via Slack, Teams, email, etc.</p>

<h3 id="capture-command-output-to-the-clipboard">Capture command output to the clipboard</h3>

<p>Simply append <code class="language-plaintext highlighter-rouge">| clip</code> to the end of the command to copy the output to the clipboard, preserving the formatting.</p>

<p>For example, instead of just running:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Process</span><span class="w">
</span></code></pre></div></div>

<p>They can run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Process</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">clip</span><span class="w">
</span></code></pre></div></div>

<p>This works for the output of any command or executable, not just PowerShell commands.
For example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dsregcmd</span><span class="w"> </span><span class="nx">/status</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">clip</span><span class="w">
</span></code></pre></div></div>

<p>This works in both PowerShell and the old-school Command Prompt.</p>

<p>In PowerShell, <code class="language-plaintext highlighter-rouge">clip</code> is the alias for <code class="language-plaintext highlighter-rouge">Set-Clipboard</code>, which is a built-in PowerShell cmdlet that copies input to the clipboard.</p>

<p>There is also a <code class="language-plaintext highlighter-rouge">clip.exe</code> executable too, so if you are in WSL or Git Bash, you can use <code class="language-plaintext highlighter-rouge">clip.exe</code> to copy the output to the Windows clipboard.
e.g.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ps | clip.exe
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">clip.exe</code> will even work in PowerShell and Command Prompt, so you can always use it in place of <code class="language-plaintext highlighter-rouge">clip</code> if you like.</p>

<p>A downside to this method is that the output is not visible in the terminal; it is only copied to the clipboard.</p>

<h3 id="capture-command-output-to-a-file">Capture command output to a file</h3>

<p>You can use the <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> operator to redirect the output of a command to a file.
e.g.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Process</span><span class="w"> </span><span class="err">&gt;&gt;</span><span class="w"> </span><span class="nx">C:\temp\output.txt</span><span class="w">
</span></code></pre></div></div>

<p>In PowerShell, the <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> operator is an alias for <code class="language-plaintext highlighter-rouge">Out-File</code>, which is a built-in PowerShell cmdlet that writes output to a text file.
e.g.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Process</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-File</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">C:\temp\output.txt</span><span class="w"> </span><span class="nt">-Append</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> operator works in other shells too, like Command Prompt and Bash, so it’s a good one to know.</p>

<p>The <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> operator will append the output to the file if it already exists, while using just <code class="language-plaintext highlighter-rouge">&gt;</code> will overwrite the file if it already exists.
Using <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> allows you to run multiple commands and capture all of the output to the same file.</p>

<p>A downside of this approach is the output is not written to the terminal; it is only written to the file.</p>

<h3 id="capture-command-output-to-both-the-terminal-and-a-file">Capture command output to both the terminal and a file</h3>

<p>We can use <code class="language-plaintext highlighter-rouge">Tee</code> to see the output in the terminal and save it to a file.
e.g.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Process</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Tee</span><span class="w"> </span><span class="nx">C:\temp\output.txt</span><span class="w">
</span></code></pre></div></div>

<p>This will write the output to both the terminal and the file.</p>

<p>In PowerShell, <code class="language-plaintext highlighter-rouge">Tee</code> is an alias for <code class="language-plaintext highlighter-rouge">Tee-Object</code>.
The full command syntax could look like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Process</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Tee-Object</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">C:\temp\output.txt</span><span class="w">
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">tee</code> exists in other shells as well like Bash, so using the shorthand syntax will work pretty much everywhere.
e.g.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ps | <span class="nb">tee </span>output.txt
</code></pre></div></div>

<h3 id="start-a-transcript-to-capture-all-output-from-the-terminal-session">Start a transcript to capture all output from the terminal session</h3>

<p>PowerShell has a built-in transcript feature that can capture all output from a terminal session and save it to a file.
This is great if you want to capture the output of multiple commands.</p>

<p>To start a transcript, simply run the <code class="language-plaintext highlighter-rouge">Start-Transcript</code> cmdlet and specify the path to the file where you want to save the transcript.
Then run the various commands you want to capture the output of.
When you’re done, run the <code class="language-plaintext highlighter-rouge">Stop-Transcript</code> cmdlet to stop the transcript and save the file.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Start-Transcript</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nx">C:\temp\transcript.txt</span><span class="w">

</span><span class="c"># Run various commands here</span><span class="w">
</span><span class="n">Get-Process</span><span class="w">
</span><span class="nx">Get-Service</span><span class="w">
</span><span class="n">dsregcmd</span><span class="w"> </span><span class="nx">/status</span><span class="w">

</span><span class="n">Stop-Transcript</span><span class="w">
</span></code></pre></div></div>

<p>The transcript text file can then be sent to you via Slack, Teams, email, etc.</p>

<p>This method only works for PowerShell.</p>

<h2 id="conclusion">Conclusion</h2>

<p>While screen sharing is often the best way to interactively help someone and see the output of commands in real time, it’s not always possible.
Taking screenshots is easy and effective, except in the case of long outputs that don’t fit on a single screen, or when you want to be able to copy and paste the output yourself.</p>

<p>Luckily, there are several easy options for capturing terminal output to a file or the clipboard, which can then be shared via Slack, Teams, email, etc.</p>

<p>I hope you’ve found this post informative and it helps you when working with others remotely!</p>

<p>Happy helping and troubleshooting! 😊</p>

<p><img src="/assets/Posts/2026-03-01-Easily-capture-terminal-output-from-users-you-help-remotely/generate-file-for-email-post-thumbnail.png" alt="Post thumbnail image" /></p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Productivity" /><category term="PowerShell" /><category term="Productivity" /><summary type="html"><![CDATA[When helping others troubleshoot issues with their PC, you often need to see the output of commands they run. The easiest way to do this is to synchronously video call and screen share, but that’s not always possible.]]></summary></entry><entry><title type="html">Colour your kubectl output and type less</title><link href="https://blog.danskingdom.com/Colour-your-kubectl-output-and-type-less/" rel="alternate" type="text/html" title="Colour your kubectl output and type less" /><published>2026-02-05T00:00:00+00:00</published><updated>2026-02-05T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Colour-your-kubectl-output-and-type-less</id><content type="html" xml:base="https://blog.danskingdom.com/Colour-your-kubectl-output-and-type-less/"><![CDATA[<p>If you work with Kubernetes on the command line using <code class="language-plaintext highlighter-rouge">kubectl</code>, you will appreciate <a href="https://kubecolor.github.io/">kubecolor</a>.
Kubecolor is a small utility that adds colour to your <code class="language-plaintext highlighter-rouge">kubectl</code> output, making it easier to read and understand at a glance.</p>

<p>Normally when you run a <code class="language-plaintext highlighter-rouge">kubectl</code> command, you get back a wall of boring grey text.
With kubecolor, you get informative, colourful output like this:</p>

<p><img src="/assets/Posts/2026-02-05-Colour-your-kubectl-output-and-type-less/kubecolor-sample-output-1.png" alt="Kubecolor sample output 1" /></p>

<p><img src="/assets/Posts/2026-02-05-Colour-your-kubectl-output-and-type-less/kubecolor-sample-output-2.png" alt="Kubecolor sample output 2" /></p>

<p>I especially appreciate how errors are highlighted red, making them easy to spot.</p>

<h2 id="kubecolor-installation">Kubecolor installation</h2>

<p>Kubecolor is cross-platform, easy to install, and has great <a href="https://kubecolor.github.io/setup/install/">installation documentation</a>.</p>

<p>For example, installing it on Windows is as easy as running:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">winget</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nt">--id</span><span class="w"> </span><span class="nx">Kubecolor.kubecolor</span><span class="w">
</span></code></pre></div></div>

<h2 id="setup-to-use-kubecolor-by-default">Setup to use kubecolor by default</h2>

<p>The Kubecolor documentation also has <a href="https://kubecolor.github.io/setup/shells/powershell/">great setup instructions</a> for many shells, including PowerShell.</p>

<p>You can typically set up an alias so that anytime you run <code class="language-plaintext highlighter-rouge">kubectl</code>, it automatically uses kubecolor.
Better yet, instead of having to type <code class="language-plaintext highlighter-rouge">kubectl</code> every time, just type <code class="language-plaintext highlighter-rouge">k</code> instead!</p>

<p>Here’s how you can set that up in PowerShell:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-Alias</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">kubectl</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nx">kubecolor</span><span class="w">
</span><span class="n">Set-Alias</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">k</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nx">kubectl</span><span class="w">
</span></code></pre></div></div>

<p>Now you can run <code class="language-plaintext highlighter-rouge">k get pods</code> and see colourful output by default.</p>

<h2 id="setup-kubectl-command-and-parameter-completions">Setup kubectl command and parameter completions</h2>

<p>The documentation also has instructions for setting up <a href="https://kubecolor.github.io/setup/shells/powershell/">kubectl completions</a> in many shells, which is super handy.</p>

<p>This is how it can be done in PowerShell:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">kubectl</span><span class="w"> </span><span class="nx">completion</span><span class="w"> </span><span class="nx">powershell</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-String</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Invoke-Expression</span><span class="w">
</span><span class="nx">Register-ArgumentCompleter</span><span class="w"> </span><span class="nt">-CommandName</span><span class="w"> </span><span class="s1">'k'</span><span class="p">,</span><span class="s1">'kubectl'</span><span class="p">,</span><span class="s1">'kubecolor'</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="bp">$_</span><span class="nx">_kubectlCompleterBlock</span><span class="w">
</span></code></pre></div></div>

<p>Now you can type <code class="language-plaintext highlighter-rouge">k get p</code> and hit <kbd>Tab</kbd> and it will auto-complete.
Continue hitting <kbd>Tab</kbd> to cycle through the available options, or use <kbd>Ctrl</kbd> + <kbd>Space</kbd> to see all available options.</p>

<p>This works for both command names and parameter values, making it easier to discover <code class="language-plaintext highlighter-rouge">kubectl</code> commands and custom resources in your cluster.
Try typing each of the following and hitting <kbd>Ctrl</kbd> + <kbd>Space</kbd>.
Note the trailing space at the end of each:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">k </code></li>
  <li><code class="language-plaintext highlighter-rouge">k get </code></li>
  <li><code class="language-plaintext highlighter-rouge">k get pods -n </code></li>
</ul>

<h2 id="automatic-setup-with-powershell-profile">Automatic setup with PowerShell profile</h2>

<p>You don’t want to have to run the setup commands every time you open PowerShell though.
You can use your PowerShell profile to run the code automatically every time PowerShell starts.</p>

<p>In PowerShell, you can find the path to your PowerShell profile by running:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">$PROFILE</span><span class="w">
</span></code></pre></div></div>

<p>To open the file in Notepad, run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">notepad</span><span class="w"> </span><span class="bp">$PROFILE</span><span class="w">
</span></code></pre></div></div>

<p>Create the file if it doesn’t exist, and add the following code to it:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># If kubecolor is installed, alias kubectl to use it.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Command</span><span class="w"> </span><span class="nx">kubecolor</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="n">Set-Alias</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">kubectl</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nx">kubecolor</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># If kubectl is installed, register the argument completions for kubectl and its aliases.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Command</span><span class="w"> </span><span class="nx">kubectl</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="n">Set-Alias</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">k</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nx">kubectl</span><span class="w">
  </span><span class="n">kubectl</span><span class="w"> </span><span class="nx">completion</span><span class="w"> </span><span class="nx">powershell</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-String</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Invoke-Expression</span><span class="w">
  </span><span class="nx">Register-ArgumentCompleter</span><span class="w"> </span><span class="nt">-CommandName</span><span class="w"> </span><span class="s1">'k'</span><span class="p">,</span><span class="s1">'kubectl'</span><span class="p">,</span><span class="s1">'kubecolor'</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="bp">$_</span><span class="nx">_kubectlCompleterBlock</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>That’s it!</p>

<p>To test it out, open a new PowerShell window and run <code class="language-plaintext highlighter-rouge">k version</code> or <code class="language-plaintext highlighter-rouge">k get pods</code>.
You should see beautiful, colourful output.</p>

<h2 id="colour-themes">Colour themes</h2>

<p>Kubecolor comes with several built-in colour themes, including light and dark themes.
It even allows you to create your own custom themes.
See <a href="https://kubecolor.github.io/customizing/themes/">the documentation</a> for more info.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So now you get beautiful, colourful <code class="language-plaintext highlighter-rouge">kubectl</code> output that is easier to read and understand at a glance.
You also get to type less by using the <code class="language-plaintext highlighter-rouge">k</code> alias, and improve discoverability with command and parameter completions.</p>

<p>I hope you found this useful.</p>

<p>Happy Kuberneting!</p>

<p>If you enjoyed this post, you might also like <a href="https://blog.danskingdom.com/Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/">this post on using Oh My Posh and updating your terminal to use different fonts</a>.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Kubernetes" /><category term="PowerShell" /><category term="Productivity" /><category term="Kubernetes" /><category term="PowerShell" /><category term="Productivity" /><summary type="html"><![CDATA[If you work with Kubernetes on the command line using kubectl, you will appreciate kubecolor. Kubecolor is a small utility that adds colour to your kubectl output, making it easier to read and understand at a glance.]]></summary></entry><entry><title type="html">Handy Windows Command Prompt commands</title><link href="https://blog.danskingdom.com/Handy-Windows-Command-Prompt-commands/" rel="alternate" type="text/html" title="Handy Windows Command Prompt commands" /><published>2026-01-25T00:00:00+00:00</published><updated>2026-01-25T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Handy-Windows-Command-Prompt-commands</id><content type="html" xml:base="https://blog.danskingdom.com/Handy-Windows-Command-Prompt-commands/"><![CDATA[<p>Below are some handy Windows Command Prompt commands that can help you troubleshoot and manage your system more effectively.</p>

<p>For commands that produce a lot of output, consider redirecting the output to a text file for easier reading.
You can do this by appending <code class="language-plaintext highlighter-rouge">&gt; filename.txt</code> to the command.
Another option is appending <code class="language-plaintext highlighter-rouge">| clip</code> when using PowerShell to copy the output directly to the clipboard so you can paste it in a text editor.</p>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>chkdsk</td>
      <td>Checks the file system for logical and physical errors. Can fix some issues automatically using <code class="language-plaintext highlighter-rouge">/f</code> or <code class="language-plaintext highlighter-rouge">/r</code> parameters.</td>
    </tr>
    <tr>
      <td>Dism /Online /Cleanup-Image /RestoreHealth</td>
      <td>Repairs the Windows image, fixing corruption and other issues that may affect system stability.</td>
    </tr>
    <tr>
      <td>gpupdate /force</td>
      <td>Forces an immediate update of Group Policy settings. Useful for applying changes without waiting for the next automatic refresh.</td>
    </tr>
    <tr>
      <td>ipconfig</td>
      <td>Displays the current network configuration, including IP address, subnet mask, and default gateway.</td>
    </tr>
    <tr>
      <td>ipconfig /displaydns</td>
      <td>Shows the contents of the DNS resolver cache.</td>
    </tr>
    <tr>
      <td>ipconfig /flushdns</td>
      <td>Clears the DNS resolver cache, which can help resolve certain connectivity issues.</td>
    </tr>
    <tr>
      <td>powercfg /batteryreport</td>
      <td>Generates a detailed battery report for laptops, showing past usage and battery health.</td>
    </tr>
    <tr>
      <td>netstat -anob</td>
      <td>Displays active network connections, their ports, and the associated app name and process ID (PID) using the port. Useful for identifying suspicious connections, and finding which application is using a particular port.</td>
    </tr>
    <tr>
      <td>sfc /scannow</td>
      <td>Scans all protected system files and replaces corrupted files with a cached copy.</td>
    </tr>
    <tr>
      <td>whoami</td>
      <td>Displays the current logged-in user name along with domain information.</td>
    </tr>
    <tr>
      <td>whoami /groups</td>
      <td>Lists all the groups the current user belongs to.</td>
    </tr>
    <tr>
      <td>winget upgrade</td>
      <td>Lists all installed applications that have available updates via Windows Package Manager.</td>
    </tr>
    <tr>
      <td>winget upgrade –all</td>
      <td>Upgrades all installed applications that have available updates via Windows Package Manager.</td>
    </tr>
    <tr>
      <td>wmic product get name</td>
      <td>Lists all installed software on the system. Useful for inventory or troubleshooting purposes.</td>
    </tr>
    <tr>
      <td>wmiobject win32_winsat</td>
      <td>Displays the Windows System Assessment Tool (WinSAT) scores, which provides hardware performance scores to quickly see a PCs overall performance.</td>
    </tr>
  </tbody>
</table>

<p>I intend to keep updating this list as I discover more useful commands.
If you know of others, please let me know in the comments.</p>

<p>If you like these, you might also like my <a href="https://blog.danskingdom.com/Common-Run-commands-to-access-various-Windows-settings-and-apps/">Common Run commands to access various Windows settings and apps post</a>.</p>

<p>I hope you find these helpful.
Happy computing!</p>

<p><img src="/assets/Posts/2026-01-25-Handy-Windows-Command-Prompt-commands/Windows-PowerShell-command-prompt-stock-image.png" alt="Windows PowerShell command prompt stock image" /></p>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="Windows" /><category term="Productivity" /><category term="Windows" /><summary type="html"><![CDATA[Below are some handy Windows Command Prompt commands that can help you troubleshoot and manage your system more effectively.]]></summary></entry><entry><title type="html">Logitech MX Master 4 vs 3s comparison by a 40 something year old</title><link href="https://blog.danskingdom.com/Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old/" rel="alternate" type="text/html" title="Logitech MX Master 4 vs 3s comparison by a 40 something year old" /><published>2025-12-06T00:00:00+00:00</published><updated>2025-12-06T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old</id><content type="html" xml:base="https://blog.danskingdom.com/Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old/"><![CDATA[<p>Ever since I got the <a href="https://www.logitech.com/en-ca/shop/p/mx-master-3s">Logitech MX Master 3s</a> in December 2023, it’s been my favourite mouse of all time, beating out my previous favourites from the 2000s, <a href="https://www.reddit.com/r/MouseReview/comments/1egvuwg/in_light_of_logitechs_forever_mouse_my_mx510_i/">the Logitech MX510</a> and later <a href="https://www.amazon.co.uk/Logitech-MX518-Optical-Gaming-Mouse/dp/B0015R8M7U">the Logitech MX518</a>, both discontinued (although there’s <a href="https://www.amazon.ca/dp/B07P32PJZD">a newer reincarnated MX518 now</a>).</p>

<p>But this isn’t about those mice; it’s about the <a href="https://www.logitech.com/en-ca/shop/p/mx-master-4">Logitech MX Master 4</a>, which was released in October 2024, and how I found it compared to the MX Master 3s.</p>

<p>This is not meant to be a comprehensive review; just my personal experience and what stood out to me.</p>

<h2 id="why-i-got-the-mx-master-3s">Why I got the MX Master 3s</h2>

<p>I’ve been using the Logitech MX Master 3s for almost 2 years now.
I got it because I started having wrist pain, and thought a more ergonomic mouse might help.</p>

<p>I first tried <a href="https://www.logitech.com/en-ca/shop/p/mx-vertical-ergonomic-mouse">the Logitech MX Vertical</a>, but didn’t like how I had to move my entire arm to move the mouse.
I then tried the MX Master 3s.
It was super comfortable, had all of the essential buttons (left, right, middle, back, forward, and scroll wheel), as well as a thumb wheel and gesture thumb button.
I loved that all of the buttons were programmable too, via the Logi Options+ app.
The thumb gesture button allows you to click it, as well as hold it and move the mouse up/down/left/right to trigger actions, so I have mine configured to control my media (play/pause, volume up/down, next/previous track).</p>

<p>After using it for a couple weeks my wrist pain went away, and I had a new favourite mouse!</p>

<blockquote>
  <p>Let my pain be a lesson kids.
Invest in an ergonomic mouse and keyboard now, <strong>before</strong> you develop problems!</p>
</blockquote>

<h2 id="a-common-problem-with-the-mx-master-3s">A common problem with the MX Master 3s</h2>

<p>I love the MX Master 3s.
After almost 2 years though, it started having flaky issues with left button clicks.
Sometimes it would not register single clicks, or would double-click when I only clicked once.
It also made dragging-and-dropping very difficult, as most times it would release the drag before I let go of the button.</p>

<p>After some research, I found that this is a common issue with the MX Master 3s and the typical fix is to replace the button switch (as shown in <a href="https://www.youtube.com/watch?v=yTrwT5ddfcM">this YouTube video</a>), which requires taking the mouse apart and soldering.
While I like to imagine myself as a DIY guy, I’ve never soldered before and don’t have the tools on hand.</p>

<p>Instead, I took the mouse apart and applied a couple pieces of tape to the mouse casing above the switch to increase the tension on it (also shown in the above YouTube video).
This helped a bit and the issues were less frequent, but didn’t totally fix the issue.
A week later I also applied a drop of WD-40 to the switch, <a href="https://www.reddit.com/r/logitech/comments/1g0c8kd/mx_master_3s_mouse_switch_replacement/">as mentioned on Reddit</a>.
This has my 3s working normally again, at least for now.</p>

<h2 id="trying-out-the-mx-master-4">Trying out the MX Master 4</h2>

<p>Since the MX Master 4 had just come out, I figured it was a good time to upgrade, as I wasn’t sure how long my temporary fix to my 3s would last.</p>

<p>Unfortunately, it sounds like the MX Master 4 uses the same button switches as the 3s, so they will likely have the same issue eventually.
Time will tell.</p>

<p>Below is a breakdown of how I found the MX Master 4 compared to the 3s.</p>

<p>✅ MX Master 4 <strong>Pros</strong>:</p>

<ul>
  <li>I like the thumb wheel better.
It sits closer to the tip of my thumb, making it more comfortable to use.</li>
  <li>Has an extra programmable gesture button in front of the forward button, which was a compelling reason to upgrade.</li>
  <li>The haptic feedback (vibration) is neat, but not really useful.
The only time I notice it is when waking the mouse and using the Actions Ring.
Right now very few apps make use of it, but that may change in the future.</li>
  <li>Screws to take it apart do not require removing the feet pads, like the 3s.
    <ul>
      <li>Uses 6 phillips screws, which is nice.
The 3s uses 5 phillips screws and 1 torx screw, which was annoying as I had to hunt down a torx screwdriver.</li>
    </ul>
  </li>
  <li>Feels very similar to the MX Master 3s in terms of shape, size, and button layout, and uses the same Logi Options+ app, so upgrading felt smooth.</li>
</ul>

<p>❌ MX Master 4 <strong>Cons</strong>:</p>

<ul>
  <li>It feels quite a bit heavier than the 3s.
The specs say it’s only 9 grams heavier (150g vs 141g), but it feels like more.
    <ul>
      <li>I was getting wrist pain a couple years back, and the 3s alleviated that for nearly 2 years.
After using the 4 for a little over a week, the wrist pain returned.</li>
    </ul>
  </li>
  <li>The thumb button is triggered by pressing <em>down</em> or <em>in</em>, so I often press it in by accident when adjusting the mouse new position on my desk, such as when changing my sitting position.
    <ul>
      <li>In the 3s, you have to press the thumb button <em>down</em>, allowing you to easily pick up the mouse (by squeezing your thumb in) without accidentally pressing the button.</li>
      <li>The 4 is both heavier and smoother than the 3s.
Even with the textured bumps on the thumb button, it’s quite slippery, making the thumb slide when picking it up and forcing you to grip it tighter, leading to accidental presses.</li>
      <li>I found this mentioned in several other online reviews as well, so I know it’s not just me.</li>
      <li>I change the thumb button to media controls (the default action is the Actions Ring), so more than once I accidentally pressed the thumb button while adjusting the mouse position on my desk and started playing music while on a video call (embarrassing! 🫠).</li>
    </ul>
  </li>
  <li>The gesture button is so far forward it’s a bit hard to reach with my thumb.
    <ul>
      <li>It’s not a huge issue, but I noticed myself having to consciously think about reaching for it.
That could change and become muscle memory over time though.</li>
    </ul>
  </li>
  <li>The scroll wheel button to switch between ratchet and free-spin modes makes a noticeable click sound, where it is silent on the 3s.
This doesn’t really bother me though; just something I noticed.</li>
</ul>

<p>The feel of the MX 4 in my hand is very similar to the 3s.
The 3s is a bit grippier due to the rubberized surface, while the MX 4 has a smoother plastic finish.
I didn’t mind the feel, but considering the MX 4 is heavier, it could have benefitted from the grippier surface.
The shape is nearly identical, with only very minor differences.</p>

<p>If I didn’t feel them side-by-side, it would be hard to tell which is which, aside from the weight difference and the feel of the thumb button.
If you like the feel of the MX 3s, you’ll likely like the MX 4 as well.</p>

<p>I’ll note that I use Bluetooth for both, so I can’t comment on the performance of the included Logi Bolt USB receiver.
I can say though that the 3s uses a USB-A port, while the 4 uses USB-C, so you may need to consider how many open ports you have.</p>

<p>Also, while I have used both mice for video games, I don’t play competitive or fast-paced games where low millisecond response times make a difference.
Both mice perform completely fine for my casual gaming needs.
Others have commented that if you need high polling rates, neither of these mice are ideal and you should invest in a proper gaming mouse.</p>

<p>Here’s a few pics to visually compare the two, with the 3s being the black one (bottom/left) and the 4 the graphite (grey) one:</p>

<p><img src="/assets/Posts/2025-12-06-Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old/mice-top-view.jpeg" alt="Top view" />
<img src="/assets/Posts/2025-12-06-Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old/mice-back-view.jpeg" alt="Back view" />
<img src="/assets/Posts/2025-12-06-Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old/mice-left-side.jpeg" alt="Left side view" />
<img src="/assets/Posts/2025-12-06-Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old/mice-right-side.jpeg" alt="Right side view" />
<img src="/assets/Posts/2025-12-06-Logitech-MX-Master-4-vs-3s-comparison-by-a-40-something-year-old/mice-side-profile.jpeg" alt="Side profile view" /></p>

<h2 id="conclusion">Conclusion</h2>

<p>While I quite liked the MX Master 4, especially for the extra programmable button, the wrist pain due to increased weight and accidental thumb button presses were a deal-breaker for me.</p>

<p>I ended up returning my MX Master 4 and will likely be ordering another 3s if the click issues return.
If I was still in my 20s and hadn’t developed wrist pain, I likely would have kept the MX 4.</p>

<p>If Logitech releases a followup version that’s a bit lighter and addresses the accidental thumb button presses, I would try it out again.</p>

<p>I don’t often do product reviews or comparisons, so if you enjoyed this leave a comment and let me know!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Hardware" /><category term="Review" /><category term="Hardware" /><category term="Review" /><category term="Logitech" /><category term="Mouse" /><summary type="html"><![CDATA[Ever since I got the Logitech MX Master 3s in December 2023, it’s been my favourite mouse of all time, beating out my previous favourites from the 2000s, the Logitech MX510 and later the Logitech MX518, both discontinued (although there’s a newer reincarnated MX518 now).]]></summary></entry><entry><title type="html">Should observability replace custom error messages?</title><link href="https://blog.danskingdom.com/Should-observability-replace-custom-error-messages-/" rel="alternate" type="text/html" title="Should observability replace custom error messages?" /><published>2025-10-19T00:00:00+00:00</published><updated>2025-10-19T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Should-observability-replace-custom-error-messages-</id><content type="html" xml:base="https://blog.danskingdom.com/Should-observability-replace-custom-error-messages-/"><![CDATA[<p>Open Telemetry (OTEL) and observability tools have come a long way in recent years.
They can provide a wealth of information about what your application was doing when an error occurred.
But can they replace good custom error messages that provide additional context about what went wrong?</p>

<h2 id="background">Background</h2>

<p>Dave Callan, a fellow Microsoft MVP, recently <a href="https://www.linkedin.com/posts/davidcallan_net-devs-when-you-expect-exactly-1-item-activity-7382327791356334080-PgYc/">asked on LinkedIn</a> if .NET developers prefer to use <code class="language-plaintext highlighter-rouge">.First()</code>, <code class="language-plaintext highlighter-rouge">.FirstOrDefault()</code>, <code class="language-plaintext highlighter-rouge">.Single()</code>, or <code class="language-plaintext highlighter-rouge">.SingleOrDefault()</code> when they expect exactly one item in a collection.</p>

<p>I’ve written about this very subject, so I commented with a link to <a href="https://blog.danskingdom.com/First-Single-and-SingleOrDefault-in-dotnet-are-harmful/">my blog post</a> where I state that I prefer to always use <code class="language-plaintext highlighter-rouge">.FirstOrDefault()</code> because it allows me to provide a descriptive custom error message, which can make troubleshooting much easier.
<a href="https://www.linkedin.com/feed/update/urn:li:ugcPost:7382327790437814272?commentUrn=urn%3Ali%3Acomment%3A%28ugcPost%3A7382327790437814272%2C7382838797274845184%29&amp;replyUrn=urn%3Ali%3Acomment%3A%28ugcPost%3A7382327790437814272%2C7383115784740417536%29&amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287382838797274845184%2Curn%3Ali%3AugcPost%3A7382327790437814272%29&amp;dashReplyUrn=urn%3Ali%3Afsd_comment%3A%287383115784740417536%2Curn%3Ali%3AugcPost%3A7382327790437814272%29">Dave replied</a> that he prefers to use <code class="language-plaintext highlighter-rouge">.Single()</code>, even though it gives a generic error message, and rely on observability tools to troubleshoot issues when they arise; especially for issues that “should never happen”.</p>

<p>This got me thinking about whether observability tools can truly replace good custom error messages.</p>

<h2 id="observability-is-awesome">Observability is awesome</h2>

<p>If you’re not familiar with <a href="https://opentelemetry.io">OpenTelemetry</a> (OTEL), it’s a standard set of APIs for collecting metrics, traces, and logs from your application, giving you observability into its behavior.
You create spans to track how long an operation takes, and add attributes to those spans to provide additional context.
The attributes can be things specific to the operation, such as the user or items involved in the operation, or more general information, such as the environment, version of the application, or the operating system it’s running on.</p>

<p>You then export the OTEL data into an observability tool, which allows you to visualize and query the data.
This allows you to answer questions like:</p>

<ul>
  <li>Which company, user, or region has the most failed requests this week?</li>
  <li>What is the average response time for requests to a specific endpoint?
Which endpoints are the slowest?</li>
  <li>What do failing requests have in common? e.g. a specific user, company, server, region, browser, app version, etc.</li>
  <li>What were the headers, parameters, or payload of a specific request?</li>
</ul>

<p>It also allows for distributed tracing, letting you see how individual requests flow through your application and its dependencies (e.g. other services, database calls, etc.), allowing you to pinpoint where things went wrong or spot potential bottlenecks between components.</p>

<p>There’s no question that adding OTEL instrumentation to your application can provide valuable insights into its behavior.
It provides additional information, allowing you to troubleshoot issues and spot trends before they become problems.
When possible, you should absolutely add OTEL instrumentation to your application.</p>

<h2 id="should-observability-replace-custom-error-messages-though">Should observability replace custom error messages though?</h2>

<p>To be clear, I’m not talking about simply catching an exception and rewriting it with a more user-friendly message.
I’m talking about including specific data that may have led to the error, such as the parameter values that were used, the dataset that was being queried, or any other relevant context that could aid in troubleshooting.</p>

<p>Here’s some typical code that would throw an exception with a generic error message:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">people</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="p">{</span> <span class="s">"Alfred Archer"</span><span class="p">,</span> <span class="s">"Billy Baller"</span><span class="p">,</span> <span class="s">"Billy Bob"</span><span class="p">,</span> <span class="s">"Cathy Carter"</span> <span class="p">};</span>
<span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="s">"Zane"</span><span class="p">;</span>

<span class="c1">// Throws generic exception: System.InvalidOperationException: Sequence contains no matching element</span>
<span class="kt">var</span> <span class="n">zane</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">First</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">));</span>
</code></pre></div></div>

<p>And modified code that provides a more descriptive custom error message:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">people</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="p">{</span> <span class="s">"Alfred Archer"</span><span class="p">,</span> <span class="s">"Billy Baller"</span><span class="p">,</span> <span class="s">"Billy Bob"</span><span class="p">,</span> <span class="s">"Cathy Carter"</span> <span class="p">};</span>
<span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="s">"Zane"</span><span class="p">;</span>

<span class="k">try</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">zane</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">First</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">InvalidOperationException</span> <span class="n">ex</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">PersonNotFoundException</span><span class="p">(</span><span class="s">$"Could not find a person named '</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s">' in the people list."</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice that the custom error message provides the name of the person that was being searched for, which can be tremendously helpful when troubleshooting.
It makes it easy to know the exact name, and verify there were no typos or unexpected characters.
e.g. They searched for “ Zane “ (with spaces), or “Zone” instead of “Zane”.</p>

<blockquote>
  <p>Be mindful to not include sensitive information in error messages, such as passwords or personal data.</p>
</blockquote>

<p>We could have also added OTEL instrumentation to the code, creating a span for the operation and adding attributes for the name being searched for.</p>

<p>Even if we had added OTEL instrumentation, I would argue that custom error messages are still valuable for a number of reasons:</p>

<h3 id="quicker-troubleshooting">Quicker troubleshooting</h3>

<ul>
  <li>Custom error messages can provide immediate context about what went wrong, and can be surfaced in different ways:
    <ul>
      <li>Shown in the observability tooling.</li>
      <li>Logged in application logs (local or remote), separate from the observability tooling.</li>
      <li>Displayed to the user, if appropriate.</li>
    </ul>
  </li>
  <li>Allows developers to identify problems faster, as they don’t need to query the observability tooling and follow breadcrumbs to find the relevant information.</li>
  <li>Potentially allows users to self-serve and resolve issues on their own, if the error message is displayed to them.
    <ul>
      <li>If a user presses a button to submit a form and sees the error message “System.InvalidOperationException: Sequence contains more than one matching element”, they may not know what to do.
However, if they see “Error: More than one user found with the email address ‘<a href="mailto:me@example.com">me@example.com</a>’”, they have a clearer understanding of the issue and can potentially resolve it themselves.</li>
      <li>Allowing users or support staff to resolve issues on their own saves both users and developers time, making everyone happier.</li>
    </ul>
  </li>
</ul>

<h3 id="do-observability-tools-have-the-data-you-need">Do observability tools have the data you need?</h3>

<ul>
  <li>Observability tooling relies on the data that is sent to it.
If the necessary data is not being captured, it may not be available when you need it.</li>
  <li>Custom error messages can provide additional context only when its needed.
    <ul>
      <li>For example, a custom error message may include the parameters that were used for a specific query, or the dataset that was being queried, whereas you may not want to log that information for every request in your observability tooling.</li>
    </ul>
  </li>
  <li>Do you have every component in the chain instrumented?
    <ul>
      <li>If your application calls an external service or third-party library that is not instrumented, it may not have the necessary data to help troubleshoot an issue.</li>
    </ul>
  </li>
</ul>

<h3 id="budgets-change-or-are-non-existent">Budgets change, or are non-existent</h3>

<ul>
  <li>Observability tooling can be expensive, and not all organizations have the budget for it.</li>
  <li>Even if your organization has the budget now, it may not in the future.</li>
  <li>Many organizations implement sampling to reduce costs (e.g. only retain 20% of traces), which means you may not have the necessary data to troubleshoot a specific issue.</li>
</ul>

<h3 id="access-to-observability-tooling">Access to observability tooling</h3>

<ul>
  <li>Not all developers may have access to the observability tooling, especially in larger organizations, or where you pay per user.
    <ul>
      <li>Does every team member have access to the tooling?
What about support staff?</li>
    </ul>
  </li>
</ul>

<h3 id="team-members-change">Team members change</h3>

<ul>
  <li>Do all team members know how to effectively use the observability tooling?
    <ul>
      <li>Team members who are familiar with the tools may leave the organization.</li>
      <li>New team members may not be familiar with the observability tools, know where to find them, or even know they exist.</li>
    </ul>
  </li>
</ul>

<h3 id="observability-tooling-changes">Observability tooling changes</h3>

<ul>
  <li>Organizations may change observability providers (e.g. New Relic to Datadog to Honeycomb to …), which means team members may need to learn new tools.</li>
  <li>Have all of the old apps and services been updated to send data to the new observability provider?</li>
</ul>

<blockquote>
  <p>Aside: I’m a huge fan of <a href="https://www.honeycomb.io">Honeycomb.io</a>.</p>
</blockquote>

<h3 id="the-type-of-application-matters">The type of application matters</h3>

<ul>
  <li>Do users expect a desktop/mobile application to be sending observability data over the network?</li>
  <li>If not stored remotely, do users expect your app to store all of that observability data locally?</li>
  <li>Users may not want an application to send or store observability data at all, due to privacy, data usage, or performance concerns.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>It probably seems obvious that it’s preferable to include information in the error message that can help troubleshoot the issue.
I can’t tell you though how many times I’ve been frustrated, both as a user and as a developer, by generic error messages that provide no context about what went wrong.</p>

<p>I’m of the opinion that the more information you can provide when an error occurs, the better.
There’s no doubt that observability can provide additional information that can help troubleshoot issues.
However, there’s no guarantee that it will provide the same information that a well-crafted custom error message can provide, or that the observability data will be available when you need it.</p>

<p>A developer spending an extra couple of minutes writing a custom error message that provides additional context can save hours, even days, of troubleshooting time later on.
Even if it’s not an error message the end-user will see, it can still be tremendously helpful for developers and support staff.</p>

<p>So the next time you’re performing an operation that may potentially throw an exception, consider whether catching and re-throwing the exception with additional context information may be beneficial.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Software Development" /><category term="Software Development" /><summary type="html"><![CDATA[Open Telemetry (OTEL) and observability tools have come a long way in recent years. They can provide a wealth of information about what your application was doing when an error occurred. But can they replace good custom error messages that provide additional context about what went wrong?]]></summary></entry><entry><title type="html">My favourite Windows shortcuts</title><link href="https://blog.danskingdom.com/My-favourite-Windows-shortcuts/" rel="alternate" type="text/html" title="My favourite Windows shortcuts" /><published>2025-09-24T00:00:00+00:00</published><updated>2025-11-17T00:00:00+00:00</updated><id>https://blog.danskingdom.com/My-favourite-Windows-shortcuts</id><content type="html" xml:base="https://blog.danskingdom.com/My-favourite-Windows-shortcuts/"><![CDATA[<p>Windows has a ton of keyboard shortcuts that can help you be more efficient.
Below are some of my favourites.</p>

<p><img src="/assets/Posts/2025-09-24-My-favourit-Windows-shortcuts/windows-shortcuts-cartoon-image.png" alt="Windows shortcuts cartoon image" /></p>

<p>Note that <kbd>Win</kbd> is the Windows key.</p>

<blockquote>
  <p>If you enjoy this post, check out my <a href="https://blog.danskingdom.com/Windows-settings-I-change-after-a-clean-install/">Windows settings I change after a clean install</a> post.</p>
</blockquote>

<h2 id="windows-shortcuts">Windows shortcuts</h2>

<p>You can use the following shortcuts regardless of the application you’re in:</p>

<ul>
  <li><kbd>Alt</kbd> + <kbd>Tab</kbd>: Cycle between open windows.</li>
  <li>
    <p><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd>: Cycle between open windows in reverse.</p>
  </li>
  <li><kbd>Win</kbd> + <kbd>Up</kbd>: Maximize the current window.</li>
  <li><kbd>Win</kbd> + <kbd>Down</kbd>: Restore / Minimize the current window.</li>
  <li><kbd>Win</kbd> + <kbd>Left</kbd> / <kbd>Right</kbd>: Snap the current window to the left or right side of the screen.</li>
  <li><kbd>Win</kbd> + <kbd>Shift</kbd> + <kbd>Left</kbd> / <kbd>Right</kbd>: Move the current window to the left or right monitor (only works with multiple monitors set up).</li>
  <li>
    <p><kbd>Win</kbd> + <kbd>Alt</kbd> + <kbd>Left</kbd> / <kbd>Right</kbd> / <kbd>Up</kbd> / <kbd>Down</kbd>: Snap the current window to a specific quadrant of the screen (Windows 11 only).</p>
  </li>
  <li><kbd>Win</kbd> + <kbd>Tab</kbd>: Open Task View to see all virtual desktops and all windows in the current virtual desktop.</li>
  <li><kbd>Win</kbd> + <kbd>Ctrl</kbd> + <kbd>D</kbd>: Create a new virtual desktop.</li>
  <li>
    <p><kbd>Win</kbd> + <kbd>Ctrl</kbd> + <kbd>Left</kbd> / <kbd>Right</kbd>: Switch between virtual desktops.</p>
  </li>
  <li><kbd>Win</kbd> + <kbd>D</kbd>: Minimize all windows; press again to restore all windows.</li>
  <li><kbd>Win</kbd> + <kbd>E</kbd>: Open File Explorer.</li>
  <li><kbd>Win</kbd> + <kbd>R</kbd>: Open the Run dialog.</li>
  <li><kbd>Win</kbd> + <kbd>P</kbd>: Open the Project menu (for connecting to external displays).</li>
  <li><kbd>Win</kbd> + <kbd>I</kbd>: Open Settings.</li>
  <li><kbd>Win</kbd> + <kbd>L</kbd>: Lock the computer.</li>
  <li>
    <p><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Esc</kbd>: Open Task Manager.</p>
  </li>
  <li><kbd>Win</kbd> + <kbd>A</kbd>: Open the Action Center.
    <ul>
      <li>
        <p>Contains controls for Wi-Fi, Bluetooth, Airplane mode, volume, brightness, and more.</p>

        <p><img src="/assets/Posts/2025-09-24-My-favourit-Windows-shortcuts/windows-action-center.png" alt="Action Center image" /></p>
      </li>
    </ul>
  </li>
  <li><kbd>Win</kbd> + <kbd>X</kbd>: Open the Quick Link menu.
    <ul>
      <li>
        <p>Contains links to many system tools, such as Installed Apps, Device Manager, Sign out, Shut down etc.</p>

        <p><img src="/assets/Posts/2025-09-24-My-favourit-Windows-shortcuts/windows-quick-links-menu.png" alt="Quick Link menu image" /></p>
      </li>
      <li>
        <p>I often use <kbd>Win</kbd> + <kbd>X</kbd> followed by <kbd>A</kbd> to open an elevated terminal window.</p>
      </li>
    </ul>
  </li>
  <li><kbd>Win</kbd> + <kbd>Period</kbd>, or <kbd>Win</kbd> + <kbd>Semicolon</kbd>: Open the emoji picker 🤩.
    <ul>
      <li>Once in the emoji picker, type to search, use <kbd>Shift</kbd> + Arrow keys to navigate, <kbd>Enter</kbd> to insert the emoji, and <kbd>Esc</kbd> to close the picker.</li>
    </ul>
  </li>
  <li><kbd>Win</kbd> + <kbd>V</kbd>: Open Clipboard history to paste things copied in the past.
    <ul>
      <li>It’s nice that this is built into Windows 10+, but I prefer to use <a href="https://sabrogden.github.io/Ditto/">Ditto Clipboard Manager</a> instead.
See my <a href="https://youtu.be/bBvKvJfWw2c?si=VCoUFqnJAV5VwnAn">YouTube video on using Ditto</a>, and the <a href="https://youtu.be/0fRL6PFGckM?si=wILKOwQg1msrnVVN">followup video showing more features</a>.</li>
    </ul>
  </li>
  <li><kbd>Win</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd>: Take a screenshot of part of the screen and edit it with the Snipping Tool.
    <ul>
      <li>I prefer to use <a href="https://getsharex.com">ShareX</a> instead, which has many more features.</li>
      <li>Windows 11 has greatly improved the Snipping Tool, including adding shapes to the image editor and the ability to take video as well.
It is a great built-in option if you don’t want to install a third-party app.</li>
      <li>On Windows 10 I would recommend <a href="https://getgreenshot.org">Greenshot</a> if you don’t want all of the advanced features of ShareX.</li>
    </ul>
  </li>
  <li><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Win</kbd> + <kbd>B</kbd>: Restart the graphics driver.
    <ul>
      <li>This is useful if your screen is not drawing properly or one of your monitors stops displaying.</li>
    </ul>
  </li>
</ul>

<p>Applications that support multiple tabs (e.g. web browsers, text editors, File Explorer on Windows 11+) often support:</p>

<ul>
  <li><kbd>Ctrl</kbd> + <kbd>Tab</kbd> to cycle between tabs in the application.</li>
  <li><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd> to cycle between tabs in reverse.</li>
</ul>

<p>The functionality may vary depending on the application though.</p>

<h2 id="taskbar-shortcuts">Taskbar shortcuts</h2>

<p>The taskbar is the bar typically at the bottom of the screen that contains the Start button, pinned applications, open windows, and the system tray.
There are a few shortcuts you can use when clicking on an application in the task bar:</p>

<ul>
  <li><kbd>Shift</kbd> + <kbd>Left-click</kbd>: Opens another instance of the application.</li>
  <li><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Left-click</kbd>: Opens another instance of the application as an administrator.</li>
  <li><kbd>Shift</kbd> + <kbd>Right-click</kbd>: Show additional context menu options, such as Run As Admin or a Different User.</li>
</ul>

<h2 id="file-explorer-shortcuts">File Explorer shortcuts</h2>

<p>When in the File Explorer, you can use the following shortcuts:</p>

<ul>
  <li><kbd>Alt</kbd> + <kbd>Up</kbd>: Go up one level in the folder hierarchy.</li>
  <li><kbd>Alt</kbd> + <kbd>Left</kbd>: Go back.</li>
  <li>
    <p><kbd>Alt</kbd> + <kbd>Right</kbd>: Go forward.</p>
  </li>
  <li><kbd>Ctrl</kbd> + <kbd>N</kbd>: Open a new File Explorer window at the current folder.</li>
  <li><kbd>Ctrl</kbd> + <kbd>T</kbd>: Open a new tab (Windows 11+).</li>
  <li>
    <p><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>N</kbd>: Create a new folder.</p>
  </li>
  <li><kbd>Alt</kbd> + <kbd>D</kbd>: Focus the address bar so you can type in it.</li>
  <li><kbd>Alt</kbd> + <kbd>Enter</kbd>: Show properties for the selected file/folder.</li>
  <li><kbd>F2</kbd>: Rename the selected file/folder.</li>
  <li>
    <p><kbd>Shift</kbd> + <kbd>Delete</kbd>: Permanently delete the selected items without moving them to the Recycle Bin.</p>
  </li>
  <li><kbd>Ctrl</kbd> + <kbd>A</kbd>: Select all items in the current view.</li>
  <li>Hold <kbd>Shift</kbd> and click to select a range of items.</li>
  <li>Hold <kbd>Ctrl</kbd> and click to select multiple individual items.</li>
  <li>Just start typing the name of a file or folder to quickly jump to and select it.</li>
</ul>

<p>Many of the above shortcuts also work in other applications, such as web browsers and text editors.</p>

<p>In the File Explorer address bar (or the Run dialog window), you can type:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">cmd</code> to open a command prompt in the current folder.</li>
  <li><code class="language-plaintext highlighter-rouge">pwsh</code> to open a PowerShell 7+ prompt in the current folder, if you have it installed.</li>
  <li><code class="language-plaintext highlighter-rouge">shell:startup</code> to open the Startup folder of the current user.
    <ul>
      <li>Placing a shortcut to an application in this folder will make it start automatically when you log in.</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">shell:sendto</code> to open the SendTo folder of the current user.
    <ul>
      <li>Placing a shortcut to an application in this folder will make it appear in the Send To context menu when you right-click a file or folder.</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">shell:common startup</code> to open the Startup folder for all users.</li>
  <li><code class="language-plaintext highlighter-rouge">shell:common sendto</code> to open the SendTo folder for all users.</li>
  <li><code class="language-plaintext highlighter-rouge">shell:desktop</code> to open the Desktop folder.</li>
  <li><code class="language-plaintext highlighter-rouge">shell:downloads</code> to open the Downloads folder.</li>
  <li><code class="language-plaintext highlighter-rouge">shell:appsfolder</code> to see all installed applications, including hidden ones, and right-click to uninstall ones you don’t need.</li>
</ul>

<p>See <a href="https://youtu.be/-ixXAB2Gc0M?si=1lJ7Bx7UJqAQ6U5O">my YouTube video on File Explorer shortcuts</a> for more!</p>

<p><img src="/assets/Posts/2025-09-24-My-favourit-Windows-shortcuts/file-explorer-address-bar.png" alt="File Explorer address bar" /></p>

<h2 id="conclusion">Conclusion</h2>

<p>Learning and using keyboard shortcuts can significantly improve your productivity.
Try to incorporate a few of these shortcuts into your daily routine.</p>

<p>As I discover move great shortcuts I’ll update this post, so be sure to check back for updates!</p>

<p>Know of a great shortcut that I missed?
Please let me know in the comments!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows" /><category term="Productivity" /><category term="Shortcuts" /><category term="Windows" /><category term="Productivity" /><category term="Shortcuts" /><summary type="html"><![CDATA[Windows has a ton of keyboard shortcuts that can help you be more efficient. Below are some of my favourites.]]></summary></entry><entry><title type="html">Create an Azure app registration secret with expiry longer than 24 months</title><link href="https://blog.danskingdom.com/Create-an-Azure-app-registration-secret-with-expiry-longer-than-24-months/" rel="alternate" type="text/html" title="Create an Azure app registration secret with expiry longer than 24 months" /><published>2025-08-01T00:00:00+00:00</published><updated>2025-08-01T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Create-an-Azure-app-registration-secret-with-expiry-longer-than-24-months</id><content type="html" xml:base="https://blog.danskingdom.com/Create-an-Azure-app-registration-secret-with-expiry-longer-than-24-months/"><![CDATA[<p>The Azure portal allows you to create app registration secrets with a maximum expiry of 24 months.
This follows the security best practice of forcing you to rotate secrets regularly to minimize the risk of exposure.
However, there are scenarios where you might want a secret that lasts longer than this limit.</p>

<blockquote>
  <p>NOTE: Using long-lived secrets can increase the risk of exposure, so it’s typically not recommended for production applications.
Especially for secrets that would grant access to sensitive resources or data, or that have high privileges.
A better approach is to use short-lived secrets and automate rotating them regularly.
Even better, use <a href="https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview">Federated Identity Credentials</a> and avoid secrets and expirations altogether.</p>
</blockquote>

<p>To create an Azure app registration (i.e. service principal) secret with an expiry longer than 2 years, you can use <a href="https://learn.microsoft.com/en-us/cli/azure/?view=azure-cli-latest">the Azure CLI</a>.
Here’s an example command that creates a secret with a 5 year expiry, where <code class="language-plaintext highlighter-rouge">&lt;application-id&gt;</code> is the Application (Client) ID of your app registration:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>az ad app credential reset <span class="nt">--id</span> &lt;application-id&gt; <span class="nt">--years</span> 5
</code></pre></div></div>

<p>This will create a new secret on the app registration and output the <code class="language-plaintext highlighter-rouge">password</code>, which is the secret value.
Be sure to copy the value and store it somewhere secure, such as an Azure Key Vault, as you will not be able to retrieve it again.</p>

<p>You can do the same operation in PowerShell using the <code class="language-plaintext highlighter-rouge">Az</code> PowerShell module (specifically the <code class="language-plaintext highlighter-rouge">Az.Resources</code> module) and <a href="https://learn.microsoft.com/en-us/powershell/module/az.resources/new-azadappcredential">the New-AzADAppCredential cmdlet</a>, but you must use the app registration’s Object ID instead of the Application ID:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$startDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="w">
</span><span class="nv">$endDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$startDate</span><span class="o">.</span><span class="nf">AddYears</span><span class="p">(</span><span class="nx">5</span><span class="p">)</span><span class="w">
</span><span class="n">New-AzADAppCredential</span><span class="w"> </span><span class="nt">-ObjectId</span><span class="w"> </span><span class="err">&lt;</span><span class="nx">object-id</span><span class="err">&gt;</span><span class="w"> </span><span class="nt">-StartDate</span><span class="w"> </span><span class="nv">$startDate</span><span class="w"> </span><span class="nt">-EndDate</span><span class="w"> </span><span class="nv">$endDate</span><span class="w"> </span><span class="nt">-DisplayName</span><span class="w"> </span><span class="s1">'User friendly description'</span><span class="w">
</span></code></pre></div></div>

<p>Hopefully you (and my future self) find this helpful.</p>

<p>Happy coding!</p>

<p><img src="/assets/Posts/2025-08-01-Create-an-Azure-app-registration-secret-with-expiry-longer-than-24-months/azure-secret-2+-years.png" alt="Azure secrets 2+ years" /></p>]]></content><author><name>Daniel Schroeder</name></author><category term="Azure" /><category term="Azure" /><summary type="html"><![CDATA[The Azure portal allows you to create app registration secrets with a maximum expiry of 24 months. This follows the security best practice of forcing you to rotate secrets regularly to minimize the risk of exposure. However, there are scenarios where you might want a secret that lasts longer than this limit.]]></summary></entry><entry><title type="html">My favourite git aliases</title><link href="https://blog.danskingdom.com/My-favourite-git-aliases/" rel="alternate" type="text/html" title="My favourite git aliases" /><published>2025-04-02T00:00:00+00:00</published><updated>2025-04-09T00:00:00+00:00</updated><id>https://blog.danskingdom.com/My-favourite-git-aliases</id><content type="html" xml:base="https://blog.danskingdom.com/My-favourite-git-aliases/"><![CDATA[<p>Git is super powerful, but can also be confusing, especially when using the command line interface (CLI).
It has so many commands, and I only regularly use a handful of them.</p>

<p>To make it both easier to remember commands I rarely use, and to reduce the number of keystrokes needed to execute the ones I use all the time, I use Git aliases.</p>

<h2 id="show-me-the-code">Show me the code!</h2>

<p>I go over each alias in detail below, but here’s the alias section taken directly from my current global <code class="language-plaintext highlighter-rouge">.gitconfig</code> file in my user directory.
e.g. <code class="language-plaintext highlighter-rouge">C:\Users\[Your Name]\.gitconfig</code> on Windows, or <code class="language-plaintext highlighter-rouge">~/.gitconfig</code> on Linux/Mac.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span><span class="nb">alias</span><span class="o">]</span>
  <span class="nb">alias</span> <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git config --get-regexp ^alias\\.'</span> <span class="o">&amp;&amp;</span> git config <span class="nt">--get-regexp</span> ^alias<span class="se">\\</span><span class="nb">.</span>
  ac <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git add -A , git commit -m'</span> <span class="o">&amp;&amp;</span> git add <span class="nt">-A</span> <span class="o">&amp;&amp;</span> git commit <span class="nt">-m</span>
  b <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git branch'</span> <span class="o">&amp;&amp;</span> git branch
  browse <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'start `git config get remote.origin.url`'</span> <span class="o">&amp;&amp;</span> start <span class="sb">`</span>git config get remote.origin.url<span class="sb">`</span>
  co <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git checkout'</span> <span class="o">&amp;&amp;</span> git checkout
  commit-empty <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git commit --allow-empty -m \"chore: Empty commit to re-trigger build\"'</span> <span class="o">&amp;&amp;</span> git commit <span class="nt">--allow-empty</span> <span class="nt">-m</span> <span class="se">\"</span>chore: Empty commit to re-trigger build<span class="se">\"</span>
  delete-local-branches-already-merged-in-remote <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git branch --merged | egrep -i -v(main|master|develop|dev|staging|release)| xargs -r git branch -d'</span> <span class="o">&amp;&amp;</span> git branch <span class="nt">--merged</span> | egrep <span class="nt">-i</span> <span class="nt">-v</span> <span class="s1">'(main|master|develop|dev|staging|release)'</span> | xargs <span class="nt">-r</span> git branch <span class="nt">-d</span>
  delete-local-branches-already-merged-in-remote-what-if <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git branch --merged | egrep -i -v(main|master|develop|dev|staging|release)//| xargs -r git branch -d'</span> <span class="o">&amp;&amp;</span> git branch <span class="nt">--merged</span> | egrep <span class="nt">-i</span> <span class="nt">-v</span> <span class="s1">'(main|master|develop|dev|staging|release)'</span>
  delete-local-tags <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git tag -l | xargs git tag -d &amp;&amp; git fetch --tags'</span> <span class="o">&amp;&amp;</span> git tag <span class="nt">-l</span> | xargs git tag <span class="nt">-d</span> <span class="o">&amp;&amp;</span> git fetch <span class="nt">--tags</span>
  delete-stale-remote-tracking-branches-from-local <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git remote prune origin'</span> <span class="o">&amp;&amp;</span> git remote prune origin
  ge <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'GitExtensions .'</span> <span class="o">&amp;&amp;</span> GitExtensions <span class="nb">.</span>
  gec <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'GitExtensions commit'</span> <span class="o">&amp;&amp;</span> GitExtensions commit
  gtfo <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git reset --hard , git clean -xfd'</span> <span class="o">&amp;&amp;</span> git reset <span class="nt">--hard</span> <span class="o">&amp;&amp;</span> git clean <span class="nt">-xfd</span>
  <span class="nb">history</span> <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git log --oneline --graph --decorate --all'</span> <span class="o">&amp;&amp;</span> git log <span class="nt">--oneline</span> <span class="nt">--graph</span> <span class="nt">--decorate</span> <span class="nt">--all</span>
  s <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git status'</span> <span class="o">&amp;&amp;</span> git status
  pushf <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git push --force-with-lease'</span> <span class="o">&amp;&amp;</span> git push <span class="nt">--force-with-lease</span>
  pushnew <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git push --set-upstream origin branch_name'</span> <span class="o">&amp;&amp;</span> git push <span class="nt">--set-upstream</span> origin <span class="sb">`</span>git symbolic-ref <span class="nt">--short</span> HEAD<span class="sb">`</span>
  stashall <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git stash push --include-untracked'</span> <span class="o">&amp;&amp;</span> git stash push <span class="nt">--include-untracked</span>
</code></pre></div></div>

<p>You will notice that each alias begins with <code class="language-plaintext highlighter-rouge">!echo '...' &amp;&amp;</code>.
This is not mandatory and you can remove it if you like; it will simply print the command to the console before executing it, so that you can see the actual commands being run.</p>

<p>For brevity, I will not include the <code class="language-plaintext highlighter-rouge">!echo '...' &amp;&amp;</code> part in the explanations below.</p>

<h2 id="updating-your-gitconfig-file">Updating your .gitconfig file</h2>

<p>To add these aliases to your global <code class="language-plaintext highlighter-rouge">.gitconfig</code> file, you can either:</p>

<ol>
  <li>Use the <code class="language-plaintext highlighter-rouge">git config</code> command to add them one by one.
    <ul>
      <li>
        <p>For example, to add the <code class="language-plaintext highlighter-rouge">ac</code> alias, you can run the following command in your terminal:</p>

        <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git config <span class="nt">--global</span> alias.ac <span class="s1">'!echo "git add -A , git commit -m" &amp;&amp; git add -A &amp;&amp; git commit -m'</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>To mass edit, open the file in a text editor and add them manually.
    <ul>
      <li>
        <p>To open the file in the default editor, use the following command:</p>

        <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git config edit <span class="nt">--global</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ol>

<h2 id="individual-aliases-in-detail">Individual aliases in detail</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">alias</span> <span class="o">=</span> git config <span class="nt">--get-regexp</span> ^alias<span class="se">\\</span><span class="nb">.</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git alias</code> will show you all the aliases you have set up in your <code class="language-plaintext highlighter-rouge">.gitconfig</code> file.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ac <span class="o">=</span> git add <span class="nt">-A</span> <span class="o">&amp;&amp;</span> git commit <span class="nt">-m</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git ac "The commit message"</code> will add all changes to the staging area and commit them with the provided message.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>b <span class="o">=</span> git branch
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git b</code> will show you all the branches in your local repository.
This alias just saves a few keystrokes.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>browse <span class="o">=</span> start <span class="sb">`</span>git config get remote.origin.url<span class="sb">`</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git browse</code> will open the remote repository in your default web browser.</p>

<p><strong>Tip:</strong> Use this to open the repository in GitHub, Azure DevOps, Bitbucket, etc. to create pull requests after committing changes to a branch and pushing them up, if you prefer the PR creation web experience to the IDE experience.</p>

<p><strong>Note:</strong> <code class="language-plaintext highlighter-rouge">start</code> is a Windows command to open a file or URL in the default application.
You may need to adjust this for the Linux/Mac equivalent, which is may be <code class="language-plaintext highlighter-rouge">open</code> for Mac and <code class="language-plaintext highlighter-rouge">xdg-open</code> for Linux.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>co <span class="o">=</span> git checkout
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git co branch_name</code> will switch to the specified branch.
This alias just saves a few keystrokes.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>commit-empty <span class="o">=</span> git commit <span class="nt">--allow-empty</span> <span class="nt">-m</span> <span class="se">\"</span>chore: Empty commit to re-trigger build<span class="se">\"</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git commit-empty</code> will create an empty commit with the message <code class="language-plaintext highlighter-rouge">chore: Empty commit to re-trigger build</code>.
This is useful for re-triggering builds in CI/CD pipelines without making any code changes.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>delete-local-branches-already-merged-in-remote <span class="o">=</span> git branch <span class="nt">--merged</span> | egrep <span class="nt">-i</span> <span class="nt">-v</span> <span class="s1">'(main|master|develop|dev|staging|release)'</span> | xargs <span class="nt">-r</span> git branch <span class="nt">-d</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git delete-local-branches-already-merged-in-remote</code> will delete all local branches that have already been merged into the remote repository, except for the specified branches (main, master, develop, dev, staging, release).
This is useful for cleaning up your local branches after merging pull requests.</p>

<p><strong>Tip:</strong> Change the branches in the <code class="language-plaintext highlighter-rouge">egrep</code> command to match your own branch names that you never want it to delete.</p>

<p><strong>Tip:</strong> If you don’t like the long verbose alias name, give it a new one! 😊</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>delete-local-branches-already-merged-in-remote-what-if <span class="o">=</span> git branch <span class="nt">--merged</span> | egrep <span class="nt">-i</span> <span class="nt">-v</span> <span class="s1">'(main|master|develop|dev|staging|release)'</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git delete-local-branches-already-merged-in-remote-what-if</code> will show you all local branches that would be deleted if you ran the <code class="language-plaintext highlighter-rouge">delete-local-branches-already-merged-in-remote</code> alias command.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>delete-local-tags <span class="o">=</span> git tag <span class="nt">-l</span> | xargs git tag <span class="nt">-d</span> <span class="o">&amp;&amp;</span> git fetch <span class="nt">--tags</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git delete-local-tags</code> will delete all local tags and fetch the latest tags from the remote repository, ensuring that your local tags exactly match the remote’s.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>delete-stale-remote-tracking-branches-from-local <span class="o">=</span> git remote prune origin
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git delete-stale-remote-tracking-branches-from-local</code> will delete any stale remote-tracking branches from your local repository.
That is, if a branch has been deleted from the remote repository, this will remove it from your local repository as well if needed.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ge <span class="o">=</span> GitExtensions <span class="nb">.</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git ge</code> will open the GitExtensions GUI for the current repository.
This requires having <a href="https://gitextensions.github.io/">GitExtensions</a> installed on your machine.</p>

<p>I use this when I’m not in an IDE, like VS Code, and want to view the history of branches and view their commits and diffs.</p>

<p><strong>Tip:</strong> If you prefer a different git tool over GitExtensions, you may be able to replace <code class="language-plaintext highlighter-rouge">GitExtensions</code> with the name of your preferred tool.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gec <span class="o">=</span> GitExtensions commit
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git gec</code> will open the GitExtensions GUI for committing changes in the current repository.
Again, this requires having <a href="https://gitextensions.github.io/">GitExtensions</a> installed on your machine.</p>

<p>I use this when I’m in the terminal and want a more rich and easy diff, stage, and commit experience than the command line provides.</p>

<p><strong>Tip:</strong> Again, if you prefer a different git tool over GitExtensions, you may be able to use it if it accepts command line arguments.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gtfo <span class="o">=</span> git reset <span class="nt">--hard</span> <span class="o">&amp;&amp;</span> git clean <span class="nt">-xfd</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git gtfo</code> will reset your current branch to the last commit, deleting any uncommitted changes and removing all untracked files and directories.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">history</span> <span class="o">=</span> git log <span class="nt">--oneline</span> <span class="nt">--graph</span> <span class="nt">--decorate</span> <span class="nt">--all</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git history</code> will show you a more compact representation of the commit history for all branches in your repository.</p>

<p>Here is a sample output of the <code class="language-plaintext highlighter-rouge">git history</code> alias:</p>

<p><img src="/assets/Posts/2025-04-02-My-favourite-git-aliases/git-history-alias-command-output.png" alt="Git history alias output" /></p>

<p>Compare that to the default <code class="language-plaintext highlighter-rouge">git log</code> output:</p>

<p><img src="/assets/Posts/2025-04-02-My-favourite-git-aliases/git-log-default-command-output.png" alt="Git log output" /></p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s <span class="o">=</span> git status
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git s</code> will show you the status of your current branch, including any uncommitted changes and the current branch name.
This alias just saves a few keystrokes.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pushf <span class="o">=</span> git push <span class="nt">--force-with-lease</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git pushf</code> will force push your changes to the remote repository, with a lease.
This is useful when you need to overwrite the remote branch with your local changes, but you want to ensure that you don’t accidentally overwrite someone else’s changes by forgetting to add <code class="language-plaintext highlighter-rouge">--force-with-lease</code>.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pushnew <span class="o">=</span> git push <span class="nt">--set-upstream</span> origin <span class="sb">`</span>git symbolic-ref <span class="nt">--short</span> HEAD<span class="sb">`</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git pushnew</code> will push your current branch to the remote repository and set the upstream branch to the same name.
This is useful when you create a new branch locally and want to push it to the remote repository for the first time, and aren’t using an IDE that does this for you.
This is similar to the <code class="language-plaintext highlighter-rouge">git push --set-upstream origin branch_name</code> command, but it automatically uses the name of the current branch.</p>

<hr />

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>stashall <span class="o">=</span> git stash push <span class="nt">--include-untracked</span>
</code></pre></div></div>

<p>Typing <code class="language-plaintext highlighter-rouge">git stashall</code> will stash all changes in your working directory, including untracked files.
This is useful when you want to temporarily save your changes without committing them, and you want to include untracked files as well.</p>

<h2 id="why-arent-you-using-powershell-commands-in-the-alias">Why aren’t you using PowerShell commands in the alias?</h2>

<p>I’m a huge proponent of PowerShell, so you may be wondering why I’m using unix commands like <code class="language-plaintext highlighter-rouge">xargs</code> and <code class="language-plaintext highlighter-rouge">egrep</code> in my aliases.
The main reason is that PowerShell is not installed by default on Linux and Mac.
Git Bash is installed with Git though, so I believe the aliases should work on all platforms and in non-PowerShell shells.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I use these aliases to make my life easier when working with Git on the command line.
Hopefully you’ve found some of them useful, or they’ve inspired you to create new ones not listed here.
Feel free to customize these aliases to fit your preferences.</p>

<p>If you have suggestions for other aliases, please leave a comment below!
If you found this post helpful, consider sharing it with friends and colleagues.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Git" /><category term="Productivity" /><category term="Git" /><category term="Productivity" /><summary type="html"><![CDATA[Git is super powerful, but can also be confusing, especially when using the command line interface (CLI). It has so many commands, and I only regularly use a handful of them.]]></summary></entry><entry><title type="html">Reclaim disk space by cleaning all your Git repos with PowerShell</title><link href="https://blog.danskingdom.com/Reclaim-disk-space-by-cleaning-all-your-Git-repos-with-PowerShell/" rel="alternate" type="text/html" title="Reclaim disk space by cleaning all your Git repos with PowerShell" /><published>2025-02-17T00:00:00+00:00</published><updated>2025-02-17T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Reclaim-disk-space-by-cleaning-all-your-Git-repos-with-PowerShell</id><content type="html" xml:base="https://blog.danskingdom.com/Reclaim-disk-space-by-cleaning-all-your-Git-repos-with-PowerShell/"><![CDATA[<p>Developers often have tens or hundreds of Git repositories on their machines.
When we are done with a project, we often forget to clean up the repository, leaving temporary files like build artifacts, logs, packages, modules, and other files taking up valuable disk space.</p>

<p>The <a href="https://git-scm.com/docs/git-clean">git clean command</a> can be used to remove untracked files and free up disk space.
However, running <code class="language-plaintext highlighter-rouge">git clean -xfd</code> in each repository manually can be a hassle.
Luckily, we can automate this process using PowerShell.</p>

<h2 id="clean-all-repos-using-the-gitclean-powershell-module">Clean all repos using the GitClean PowerShell module</h2>

<p>To make it easy to clean all your Git repositories, I created the cross-platform <a href="https://github.com/deadlydog/PowerShell.GitClean">GitClean PowerShell module</a>.</p>

<p>Assuming all of your Git repositories are located under a common root folder, <code class="language-plaintext highlighter-rouge">C:\dev\Git</code> for example, you can install the module and clean all your repos with the following PowerShell code:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">GitClean</span><span class="w">
</span><span class="n">Invoke-GitClean</span><span class="w"> </span><span class="nt">-RootDirectoryPath</span><span class="w"> </span><span class="s1">'C:\dev\Git'</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2025-02-17-Reclaim-disk-space-by-cleaning-all-your-Git-repos-with-PowerShell/invoke-gitclean-with-root-directory-and-informational-messages-parameters.png" alt="Screenshot of running the Invoke-GitClean command" /></p>

<p>In the screenshot above you can see it found 262 Git repositories, cleaned 260 of them, skipped cleaning 2 of them because they had untracked files that would have been lost, and freed up 33.4 GB of disk space.
Easy 😊</p>

<p>No more node_modules of old projects taking up all my disk space 😅</p>

<p><img src="/assets/Posts/2025-02-17-Reclaim-disk-space-by-cleaning-all-your-Git-repos-with-PowerShell/node_modules-size-meme.jpeg" alt="meme of node_modules directory being huge" /></p>

<h2 id="alternative-powershell-one-liner">Alternative PowerShell one-liner</h2>

<p>Not everyone likes installing modules on their system, so as an alternative, here is a simple PowerShell script you can run to clean all your Git repositories:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$rootDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\dev\Git'</span><span class="w">

</span><span class="c"># Find all '.git' directories below the given path.</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$rootDirectoryPath</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Depth</span><span class="w"> </span><span class="nx">3</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-Directory</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s1">'.git'</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="c"># Get the directory path of the Git repository.</span><span class="w">
    </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$gitDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w">
        </span><span class="nv">$gitRepositoryDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$gitDirectoryPath</span><span class="w"> </span><span class="nt">-Parent</span><span class="w">
        </span><span class="n">Write-Output</span><span class="w"> </span><span class="nv">$gitRepositoryDirectoryPath</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="c"># Only include Git repositories that do not have untracked files.</span><span class="w">
    </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$gitOutput</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Invoke-Expression</span><span class="w"> </span><span class="s2">"git -C ""</span><span class="bp">$_</span><span class="s2">"" status"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-String</span><span class="w">
        </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="w"> </span><span class="nv">$hasUntrackedFiles</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$gitOutput</span><span class="o">.</span><span class="nf">Contains</span><span class="p">(</span><span class="s1">'Untracked files'</span><span class="p">)</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$hasUntrackedFiles</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Skipping Git repository with untracked files: </span><span class="bp">$_</span><span class="s2">"</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="n">Write-Output</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$hasUntrackedFiles</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="c"># Clean the Git repository.</span><span class="w">
    </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"Cleaning Git repository: </span><span class="bp">$_</span><span class="s2">"</span><span class="w">
        </span><span class="n">Invoke-Expression</span><span class="w"> </span><span class="s2">"git -C ""</span><span class="bp">$_</span><span class="s2">"" clean -xdf"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can <a href="/assets/Posts/2025-02-17-Reclaim-disk-space-by-cleaning-all-your-Git-repos-with-PowerShell/CleanGitRepositories.ps1">download this snippet as a script here</a>.</p>

<p>This won’t give you the nice progress bars and detailed information that the GitClean module does, but it will get the job done.</p>

<h2 id="conclusion">Conclusion</h2>

<p>It’s easy to forget to clean up old Git repositories.
When you do remember to, the GitClean module can make it quick and painless.
You might even want to create a scheduled task or cron job to run it automatically every month to keep your disk space in check.</p>

<p>I hope you find this module useful.
Happy coding! 😊</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Git" /><category term="PowerShell" /><category term="Productivity" /><category term="Git" /><category term="PowerShell" /><category term="Productivity" /><summary type="html"><![CDATA[Developers often have tens or hundreds of Git repositories on their machines. When we are done with a project, we often forget to clean up the repository, leaving temporary files like build artifacts, logs, packages, modules, and other files taking up valuable disk space.]]></summary></entry><entry><title type="html">Windows settings I change after a clean install</title><link href="https://blog.danskingdom.com/Windows-settings-I-change-after-a-clean-install/" rel="alternate" type="text/html" title="Windows settings I change after a clean install" /><published>2025-02-08T00:00:00+00:00</published><updated>2026-02-11T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Windows-settings-I-change-after-a-clean-install</id><content type="html" xml:base="https://blog.danskingdom.com/Windows-settings-I-change-after-a-clean-install/"><![CDATA[<p>Windows has a ton of native settings and features that can make it more pleasant and efficient to use.
Below are some of my favorites that I always adjust after a fresh install of Windows.
These are all personal preference, and I show the settings I like, but use whatever works best for you.</p>

<p>This post was written with Windows 11 in mind, so some of the settings may not be available in other versions of Windows, or you may need to access them differently.</p>

<p>As I discover more awesome settings I’ll add them here, so be sure to check back from time to time.
Also, if you have a favourite that I don’t have listed here, please let me know in the comments below.</p>

<blockquote>
  <p>If you enjoy this post, check out my <a href="https://blog.danskingdom.com/My-favourite-Windows-shortcuts/">My favourite Windows shortcuts</a> post.</p>
</blockquote>

<h2 id="file-explorer-settings">File Explorer settings</h2>

<h3 id="have-file-explorer-open-to-the-this-pc-view">Have File Explorer open to the This PC view</h3>

<p>By default, File Explorer opens to the <code class="language-plaintext highlighter-rouge">Home</code> view, which shows a bunch of things, such as your most used folders and files.
I find that view very cluttered, and often just want to get into the root of one of my drives, so I prefer to start at the <code class="language-plaintext highlighter-rouge">This PC</code> view.</p>

<p>Below are screenshots of the default <code class="language-plaintext highlighter-rouge">Home</code> view and the <code class="language-plaintext highlighter-rouge">This PC</code> view:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/file-explorer-home-view.png" alt="File Explorer Home view" /></p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/file-explorer-this-pc-view.png" alt="File Explorer This PC view" /></p>

<p>To change the default view of File Explorer to <code class="language-plaintext highlighter-rouge">This PC</code>:</p>

<ol>
  <li>In the Start Menu, search for <code class="language-plaintext highlighter-rouge">File Explorer Options</code> and open it.</li>
  <li>In the File Explorer Options window, change the <code class="language-plaintext highlighter-rouge">Open File Explorer to</code> setting to <code class="language-plaintext highlighter-rouge">This PC</code>.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/file-explorer-options-default-to-this-pc-view.png" alt="File Explorer Options window to change the default view to This PC" /></p>

<h3 id="show-hidden-files-and-folders-and-file-extensions-in-file-explorer">Show hidden files and folders and file extensions in File Explorer</h3>

<p>By default, File Explorer will not show hidden files and folders, nor show file extensions for known file types.
I don’t like Windows hiding things from me, so I always enable these settings.</p>

<p>Below are screenshots showing how a directory in File Explorer might look before and after making the changes:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/directory-before-showing-hidden-files-and-file-extensions.png" alt="Before showing hidden files and file extensions" /></p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/directory-after-showing-hidden-files-and-file-extensions.png" alt="After showing hidden files and file extensions" /></p>

<p>To enable the settings:</p>

<ol>
  <li>Open File Explorer Options.
    <ul>
      <li>You can search for <code class="language-plaintext highlighter-rouge">File Explorer Options</code> in the Start Menu, or find it in File Explorer by clicking the <code class="language-plaintext highlighter-rouge">View</code> tab, then <code class="language-plaintext highlighter-rouge">Options</code> on the right side (Windows 10), or the <code class="language-plaintext highlighter-rouge">...</code> button then <code class="language-plaintext highlighter-rouge">Options</code> (Windows 11).</li>
    </ul>
  </li>
  <li>In the Folder Options window, click on the <code class="language-plaintext highlighter-rouge">View</code> tab.</li>
  <li>Under <code class="language-plaintext highlighter-rouge">Advanced settings</code>, find the following settings:
    <ul>
      <li>Enable <code class="language-plaintext highlighter-rouge">Show hidden files, folders, and drives</code></li>
      <li>Disable <code class="language-plaintext highlighter-rouge">Hide extensions for known file types</code></li>
    </ul>
  </li>
  <li>Click <code class="language-plaintext highlighter-rouge">Apply</code> to apply it to the current folder.</li>
  <li>Click <code class="language-plaintext highlighter-rouge">Apply to Folders</code> to apply it to all folders.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/steps-to-show-hidden-files-and-file-extensions.png" alt="Screenshot showing how to enable showing hidden files and file extensions" /></p>

<p>The last step of clicking the <code class="language-plaintext highlighter-rouge">Apply to Folders</code> button is important so that you don’t have to do this for every folder you open.</p>

<h2 id="mouse-settings">Mouse settings</h2>

<h3 id="show-where-the-mouse-cursor-is-with-the-ctrl-key">Show where the mouse cursor is with the <kbd>Ctrl</kbd> key</h3>

<p>If you have multiple monitors, it’s easy to lose the mouse cursor.
Windows has a feature that when you press the <kbd>Ctrl</kbd> key, it will show you where the mouse cursor is by briefly circling it.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/show-where-mouse-cursor-is.gif" alt="Windows showing where the mouse cursor is" /></p>

<p>To enable this feature:</p>

<ol>
  <li>Use the Start Menu to search for <code class="language-plaintext highlighter-rouge">Mouse settings</code>.</li>
  <li>In the Mouse settings window, open the <code class="language-plaintext highlighter-rouge">Additional mouse settings</code>, which should pop up the <code class="language-plaintext highlighter-rouge">Mouse Properties</code> window.</li>
  <li>Click on the <code class="language-plaintext highlighter-rouge">Pointer Options</code> tab.</li>
  <li>Enable <code class="language-plaintext highlighter-rouge">Show location of pointer when I press the CTRL key</code>.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/mouse-properties-window-enabling-show-location-of-pointer-with-ctrl-key.png" alt="Mouse properties window showing how to enable showing the mouse cursor location" /></p>

<p>Bonus: See the Microsoft PowerToys section at the end to allow spotlighting the mouse cursor when you press the <kbd>Ctrl</kbd> key twice.</p>

<h3 id="make-the-mouse-cursor-larger-and-change-the-color">Make the mouse cursor larger and change the color</h3>

<p>Having a large resolution on a small monitor can make it difficult to see the mouse cursor.
I like to enlarge my cursor a bit, and change the color to make it stand out so it’s easier to see.</p>

<p>To adjust your mouse cursor settings, use the Start Menu to search for <code class="language-plaintext highlighter-rouge">Mouse pointer size</code>.
There you can adjust the size and color of the mouse cursor.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/mouse-pointer-size-settings.png" alt="Windows mouse cursor settings" /></p>

<h3 id="disable-the-touchpad-when-using-a-mouse">Disable the Touchpad when using a mouse</h3>

<p>When using Windows on a laptop without an external keyboard, I sometimes accidentally touch the touchpad while typing, which causes the cursor to jump around.
To prevent this, I disable the touchpad when a mouse is connected.</p>

<p>To disable the touchpad when a mouse is connected:</p>

<ol>
  <li>Use the Start Menu to search for <code class="language-plaintext highlighter-rouge">Touchpad settings</code>.</li>
  <li>Expand the <code class="language-plaintext highlighter-rouge">Touchpad</code> section.</li>
  <li>Uncheck <code class="language-plaintext highlighter-rouge">Leave touchpad on when a mouse is connected</code>.</li>
</ol>

<h3 id="change-touchpad-triple-tap-to-middle-click">Change Touchpad triple-tap to middle-click</h3>

<p>By default Windows opens the Windows search when you triple-tap the touchpad.
That’s what the Windows key is for.
Instead, I prefer to have the triple-tap act as a middle-click.</p>

<p>To change the touchpad triple-tap to middle-click:</p>

<ol>
  <li>In the touchpad settings window, expand the <code class="language-plaintext highlighter-rouge">Three-finger gestures</code> section.</li>
  <li>Change the <code class="language-plaintext highlighter-rouge">Taps</code> dropdown to <code class="language-plaintext highlighter-rouge">Middle mouse button</code>.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/touchpad-disable-when-mouse-is-connected-and-set-triple-tap-to-middle-click.png" alt="Touchpad settings to disable the touchpad when a mouse is connected and set triple-tap to middle-click" /></p>

<h2 id="taskbar-settings">Taskbar settings</h2>

<p>The Windows taskbar has many settings that can be adjusted.</p>

<p>To open the taskbar settings window:</p>

<ol>
  <li>Right-click on the taskbar and choose <code class="language-plaintext highlighter-rouge">Taskbar settings</code>, or you can search the Start Menu for <code class="language-plaintext highlighter-rouge">Taskbar settings</code>.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/taskbar-settings-to-hide-search-task-view-and-widgets.png" alt="Taskbar settings I always adjust" /></p>

<h3 id="hide-the-search-box-from-the-taskbar">Hide the search box from the taskbar</h3>

<p>When I want to search, I use the <kbd>Win</kbd> key and start typing, so I don’t like the search box taking up space on the taskbar.</p>

<p>In the taskbar settings window, in the <code class="language-plaintext highlighter-rouge">Taskbar items</code> section, change the <code class="language-plaintext highlighter-rouge">Search</code> to <code class="language-plaintext highlighter-rouge">Hide</code>.</p>

<h3 id="hide-the-task-view-button">Hide the task view button</h3>

<p>I don’t use multiple task views, and use <kbd>Alt</kbd> + <kbd>Tab</kbd> to switch between windows, so I hide the task view button.
You can also use <kbd>Win</kbd> + <kbd>Tab</kbd> to open the task view.</p>

<p>In the taskbar settings window, in the <code class="language-plaintext highlighter-rouge">Taskbar items</code> section, toggle the <code class="language-plaintext highlighter-rouge">Task view</code> to <code class="language-plaintext highlighter-rouge">Off</code>.</p>

<h3 id="disable-widgets">Disable widgets</h3>

<p>I don’t use the widgets on the taskbar and find them distracting, so I disable them.</p>

<p>In the taskbar settings window, in the <code class="language-plaintext highlighter-rouge">Taskbar items</code> section, toggle the <code class="language-plaintext highlighter-rouge">Widgets</code> to <code class="language-plaintext highlighter-rouge">Off</code>.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/task-bar-widgets-search-box-and-task-view.png" alt="Screenshot showing the widgets, search box, and task view button on the taskbar" /></p>

<h3 id="decide-which-system-tray-icons-to-always-show">Decide which system tray icons to always show</h3>

<p>The system tray is the area on the right side of the taskbar where you can see the time, volume, network status, and other system icons.
There are some tray icons that I always want to see, and others that I don’t care about.
You can control which ones are always shown, and which ones are hidden behind the <code class="language-plaintext highlighter-rouge">Show hidden icons</code> button to save space on the taskbar.</p>

<p>In the taskbar settings window, expand the <code class="language-plaintext highlighter-rouge">Other system tray icons</code> section, then toggle <code class="language-plaintext highlighter-rouge">On</code> the apps that you want to always show, and <code class="language-plaintext highlighter-rouge">Off</code> the ones to hide.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/system-tray-icons.png" alt="System tray icons" /></p>

<p>You can also simply drag-and-drop the system tray icons to/from the hidden area to control which ones are always shown.</p>

<h3 id="shift-the-start-menu-to-the-left">Shift the Start Menu to the left</h3>

<p>Windows 11 moved the Start Menu to the center of the taskbar by default.
I prefer the previous Windows behaviour where it was on the left.</p>

<p>In the taskbar settings window, in the <code class="language-plaintext highlighter-rouge">Taskbar behaviors</code> section, change the <code class="language-plaintext highlighter-rouge">Taskbar alignment</code> to <code class="language-plaintext highlighter-rouge">Left</code>.</p>

<h3 id="never-group-taskbar-buttons">Never group taskbar buttons</h3>

<p>I like to see all of my taskbar buttons including their labels, so I disable grouping them so it’s easy to see which ones are open and quickly select the one I want.</p>

<p>Here is a screenshot of the taskbar with the buttons combined:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/taskbar-buttons-combined.png" alt="Taskbar with combined taskbar icons" /></p>

<p>And the same taskbar with the buttons not combined:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/taskbar-buttons-never-combined.png" alt="Taskbar with taskbar icons never combined" /></p>

<p>In the taskbar settings window, in the <code class="language-plaintext highlighter-rouge">Taskbar behaviors</code> section, change the <code class="language-plaintext highlighter-rouge">Combine taskbar buttons and hide labels</code> to <code class="language-plaintext highlighter-rouge">Never</code>.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/taskbar-settings-never-combine-taskbar-buttons.png" alt="Taskbar settings to never group taskbar icons" /></p>

<h3 id="enable-end-task-on-right-clicking-taskbar-icons">Enable End Task on right-clicking taskbar icons</h3>

<p>Typically when an application hangs, you need to open the Task Manager to kill the app.
You can enable <code class="language-plaintext highlighter-rouge">End Task</code> in the right-click context menu of taskbar icons to make it easier to kill unresponsive apps.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/end-task-in-taskbar.png" alt="Screenshot of right-clicking a taskbar icon to show the End Task option" /></p>

<p>To enable <code class="language-plaintext highlighter-rouge">End Task</code> on right-clicking taskbar icons, go to <code class="language-plaintext highlighter-rouge">Settings</code> &gt; <code class="language-plaintext highlighter-rouge">System</code> &gt; <code class="language-plaintext highlighter-rouge">For developers</code> and enable <code class="language-plaintext highlighter-rouge">End Task</code>.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/enable-end-task-in-taskbar.png" alt="Enable End Task on right-clicking taskbar icons" /></p>

<h2 id="desktop-settings">Desktop settings</h2>

<h3 id="change-your-desktop-background-image">Change your desktop background image</h3>

<p>By default Windows will often display a different desktop background image each day from Bing.
I actually quite like this functionality for my personal computer, but prefer to have a static image on my work computer where I often do screen sharing.
You may also want to adjust the desktop image if you remote desktop to other computers regularly, so that it’s easy to tell when you are working on your local computer or a remote one.</p>

<p>To change the desktop background image:</p>

<ol>
  <li>Right-click on the desktop and choose <code class="language-plaintext highlighter-rouge">Personalize</code>.</li>
  <li>In the Personalization settings window, click on <code class="language-plaintext highlighter-rouge">Background</code>.
    <ol>
      <li>You could also get here by searching the Start Menu for <code class="language-plaintext highlighter-rouge">Background image settings</code>.</li>
    </ol>
  </li>
  <li>In the <code class="language-plaintext highlighter-rouge">Personalize your background</code> section, choose <code class="language-plaintext highlighter-rouge">Picture</code> from the dropdown.
    <ol>
      <li>If you want to cycle through multiple images, you can choose <code class="language-plaintext highlighter-rouge">Slideshow</code> instead.</li>
    </ol>
  </li>
  <li>Click the <code class="language-plaintext highlighter-rouge">Browse</code> button to select a picture or directory from your computer.</li>
  <li>You may want to change the <code class="language-plaintext highlighter-rouge">Choose a fit</code> setting to <code class="language-plaintext highlighter-rouge">Fill</code>, <code class="language-plaintext highlighter-rouge">Fit</code>, or <code class="language-plaintext highlighter-rouge">Stretch</code> to make the image look better on your screen.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/personalize-background-image-settings.png" alt="Windows background image settings" /></p>

<h3 id="turn-transparency-effects-off">Turn transparency effects off</h3>

<p>I don’t like transparency in my windows and menus, and you get a small performance boost by disabling it.</p>

<p>To disable transparency effects:</p>

<ol>
  <li>Right-click on the desktop and choose <code class="language-plaintext highlighter-rouge">Personalize</code>.</li>
  <li>In the Personalization settings window, click on <code class="language-plaintext highlighter-rouge">Colors</code>.</li>
  <li>Toggle the <code class="language-plaintext highlighter-rouge">Transparency effects</code> switch to <code class="language-plaintext highlighter-rouge">Off</code>.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/transparency-effects-disabled.png" alt="Windows transparency effects disabled" /></p>

<h2 id="power-settings">Power settings</h2>

<h3 id="dont-turn-off-the-screen-and-go-to-sleep-so-fast">Don’t turn off the screen and go to sleep so fast</h3>

<p>Windows will often turn the monitor off or put the computer to sleep after a short period of inactivity, such as 5 or 15 minutes depending on if it is on battery or not.
I like to extend this time so that if I walk away from my computer for a little while, it’s less likely to be asleep when I come back.</p>

<p>To change the power settings:</p>

<ol>
  <li>Use the Start Menu to search for <code class="language-plaintext highlighter-rouge">Power settings</code> and open the <code class="language-plaintext highlighter-rouge">Power, sleep, and battery settings</code>.</li>
  <li>Expand the <code class="language-plaintext highlighter-rouge">Screen and sleep</code> section if needed.</li>
  <li>Adjust when the computer turns off the screen and goes to sleep to your liking.</li>
</ol>

<h3 id="turn-on-enhanced-performance-power-mode">Turn on enhanced performance power mode</h3>

<p>In that same <code class="language-plaintext highlighter-rouge">Power, sleep, and battery settings</code> window, you can adjust the <code class="language-plaintext highlighter-rouge">Power mode</code> to prioritize performance over battery life and energy efficiency if you like.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/power-settings.png" alt="Windows power settings" /></p>

<h3 id="turn-the-computer-off-when-i-press-the-power-button">Turn the computer off when I press the power button</h3>

<p>By default, Windows will put the computer to sleep when you close the lid, or press the power button.
I prefer to have the computer actually shut down when I press the power button.
You may want your laptop to do nothing when the lid is closed, so it keeps running.</p>

<p>To change the power button and lid settings:</p>

<ol>
  <li>Use the Start Menu to search for <code class="language-plaintext highlighter-rouge">Close lid</code> and open the <code class="language-plaintext highlighter-rouge">Change what closing the lid does</code> option.</li>
  <li>Adjust the <code class="language-plaintext highlighter-rouge">When I close the lid</code> and <code class="language-plaintext highlighter-rouge">When I press the power button</code> settings to your liking.</li>
  <li>Click <code class="language-plaintext highlighter-rouge">Save changes</code> to apply the settings.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/power-button-and-lid-settings.png" alt="Power button and lid settings" /></p>

<h2 id="windows-features">Windows features</h2>

<h3 id="show-multiple-time-zones-in-the-taskbar-clock">Show multiple time zones in the taskbar clock</h3>

<p>You can configure multiple time zones to show in the taskbar clock when you hover over it or click on it.
This is great if you work with people in different time zones, travel frequently, or are a software developer and just want to know what the current UTC time is 😄.</p>

<p>To add additional time zones:</p>

<ol>
  <li>Right-click on the taskbar clock and choose <code class="language-plaintext highlighter-rouge">Adjust date/time</code>.
    <ol>
      <li>You can also search the Start Menu for <code class="language-plaintext highlighter-rouge">Date &amp; time settings</code>.</li>
    </ol>
  </li>
  <li>In the <code class="language-plaintext highlighter-rouge">Date &amp; time</code> settings window, scroll down and click on the <code class="language-plaintext highlighter-rouge">Additional clocks</code> section.</li>
  <li>The <code class="language-plaintext highlighter-rouge">Date and Time</code> window should open, allowing you to add up to 2 additional clocks and give them friendly names.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/date-and-time-settings-additional-clocks.png" alt="Date and Time settings with additional clocks" /></p>

<p>The result when hovering the mouse over the clock:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/date-and-time-additional-clocks-hover.png" alt="Hovering over the clock to show additional time zones" /></p>

<p>And when clicking on the clock:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/date-and-time-additional-clocks-click.png" alt="Clicking on the clock to show additional time zones" /></p>

<h3 id="use-the-windows-sandbox-to-test-software-and-settings-safely">Use the Windows Sandbox to test software and settings safely</h3>

<p>Windows includes an app called <code class="language-plaintext highlighter-rouge">Windows Sandbox</code>.
When you open the Windows Sandbox app, it creates a clean virtual environment that looks like a fresh install of Windows.
Here you can experiment with software, settings, or files without affecting your main system, and when you close the app everything is erased.
This is great for testing software or settings that you’re not sure about, without risking your main system.
Just be sure not to save anything important in the Windows Sandbox, as it will be erased when you close it.</p>

<p>The Windows Sandbox app is not installed by default, so you may need to install it from the Windows Features:</p>

<ol>
  <li>Search the Start Menu for <code class="language-plaintext highlighter-rouge">Windows Features</code>, and choose <code class="language-plaintext highlighter-rouge">Turn Windows features on or off</code>.</li>
  <li>In the Windows Features window, scroll down and enable <code class="language-plaintext highlighter-rouge">Windows Sandbox</code>.</li>
  <li>Click <code class="language-plaintext highlighter-rouge">OK</code> to install it.</li>
  <li>Restart your computer if prompted.</li>
</ol>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/windows-features-enable-windows-sandbox.png" alt="The Windows Features window showing Windows Sandbox enabled" /></p>

<p>Now search for <code class="language-plaintext highlighter-rouge">Windows Sandbox</code> in the Start Menu to open the app.</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/windows-sandbox-app.png" alt="Windows Sandbox app" /></p>

<h2 id="registry-tweaks-that-dont-have-a-gui-setting">Registry tweaks that don’t have a GUI setting</h2>

<blockquote>
  <p>NOTE: Modifying the registry can be dangerous if you don’t know what you’re doing, so be sure to <a href="https://www.tweaking.com/how-to-backup-whole-registry-in-windows-10-step-by-step-guide/">backup your registry</a> before making any manual changes.</p>

  <p>You can try the commands below in the Windows Sandbox first to see if you like the changes.</p>
</blockquote>

<p>I provide terminal commands instead of manual point-and-click steps for modifying the registry to help ensure that only the intended keys are modified.
You can run these commands in PowerShell or Command Prompt.</p>

<p>Some commands shown below may need to be ran from an elevated terminal (e.g. run PowerShell as an Administrator), otherwise you may get an error like <code class="language-plaintext highlighter-rouge">Requested registry access is not allowed</code>.
Also, you may need to restart your computer for some changes to take effect.</p>

<p>If you are curious about the syntax of the registry commands, you can find <a href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/reg">the reg.exe MS docs here</a>.</p>

<p>If you want to easily apply all of the registry tweaks below, simply <a href="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/All_registry_changes.reg">download this .reg file</a> and double-click it to run it.</p>

<h3 id="disable-searching-the-web-in-the-start-menu-search">Disable searching the web in the Start Menu search</h3>

<p>When I search from the Start Menu, I want it to only search my local machine.</p>

<p>Here are screenshots before and after disabling the web search:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/start-menu-search-before-disabling-web-search.png" alt="Before disabling web search" /></p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/start-menu-search-after-disabling-web-search.png" alt="After disabling web search" /></p>

<p>Run the following command to disable web search in the Start Menu:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="s2">"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">BingSearchEnabled</span><span class="w"> </span><span class="nx">/t</span><span class="w"> </span><span class="nx">REG_DWORD</span><span class="w"> </span><span class="nx">/d</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p>To revert back to the default behavior, run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">delete</span><span class="w"> </span><span class="s2">"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">BingSearchEnabled</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p>Note that it also disables Copilot, which may or may not be desired.</p>

<h3 id="always-open-more-options-context-menu-in-windows-11">Always open “More options” context menu in Windows 11+</h3>

<p>Windows 11 modified the right-click context menu to only show a few options by default, and you have to click “Show more options” to see the rest.
I prefer the previous Windows behavior where all options are shown by default.</p>

<p>Below are screenshots of the context menu before and after running the command.</p>

<p>Default right-click context menu:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/right-click-context-menu-default.png" alt="Default right-click context menu" /></p>

<p>Right-click context menu with all options shown:</p>

<p><img src="/assets/Posts/2025-02-08-Windows-settings-I-change-after-a-clean-install/right-click-context-menu-with-all-options-shown.png" alt="Right-click context menu with all options shown" /></p>

<p>Run the following command to always show all options in the context menu:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="s2">"HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32"</span><span class="w"> </span><span class="nx">/f</span><span class="w"> </span><span class="nx">/ve</span><span class="w">
</span></code></pre></div></div>

<p>To revert back to the default behavior, run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">delete</span><span class="w"> </span><span class="s2">"HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}"</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p><a href="https://www.elevenforum.com/t/disable-show-more-options-context-menu-in-windows-11.1589/">Source and more info</a></p>

<blockquote>
  <p>NOTE: Windows 11 updated to allow pressing <kbd>Shift</kbd> + <kbd>Right-click</kbd> to show all options in the context menu, so you may prefer to do that over using this registry tweak.</p>
</blockquote>

<h3 id="speed-up-the-right-click-context-menu">Speed up the right-click context menu</h3>

<p>When you right-click a file or folder, Windows has an artificial delay of 400 milliseconds before showing the context menu.</p>

<p>Run the following command to change the delay to 50 milliseconds:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="s2">"HKCU\Control Panel\Desktop"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">MenuShowDelay</span><span class="w"> </span><span class="nx">/t</span><span class="w"> </span><span class="nx">REG_SZ</span><span class="w"> </span><span class="nx">/d</span><span class="w"> </span><span class="nx">50</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p>To revert back to the default behavior, run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="s2">"HKCU\Control Panel\Desktop"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">MenuShowDelay</span><span class="w"> </span><span class="nx">/t</span><span class="w"> </span><span class="nx">REG_SZ</span><span class="w"> </span><span class="nx">/d</span><span class="w"> </span><span class="nx">400</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p>Also, before the context menu is shown, the <code class="language-plaintext highlighter-rouge">Send to</code> menu discovers all of the apps and connected devices you can send the file to before it actually shows the menu.
We can change this so that it does not enumerate devices until you hover over the <code class="language-plaintext highlighter-rouge">Send to</code> menu.</p>

<p>Run the following command to delay Send To from looking for devices right away:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="s2">"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">DelaySendToMenuBuild</span><span class="w"> </span><span class="nx">/t</span><span class="w"> </span><span class="nx">REG_DWORD</span><span class="w"> </span><span class="nx">/d</span><span class="w"> </span><span class="nx">1</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p>To revert back to the default behavior, run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">delete</span><span class="w"> </span><span class="s2">"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">DelaySendToMenuBuild</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p><a href="https://www.makeuseof.com/windows-11-menus-felt-sluggish-until-i-tweaked-this-one-registry/">Source</a> and <a href="https://www.winhelponline.com/blog/hidden-registry-settings-sendto-menu-windows-7/">more info</a></p>

<h3 id="disable-delay-of-startup-apps">Disable delay of startup apps</h3>

<p>Windows puts a delay of 10 seconds before it starts launching apps in the Startup folder.</p>

<p>Run the following command to disable the startup apps delay:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="s2">"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Serialize"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">Startupdelayinmsec</span><span class="w"> </span><span class="nx">/t</span><span class="w"> </span><span class="nx">REG_DWORD</span><span class="w"> </span><span class="nx">/d</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p>To revert back to the default behavior, run:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">reg.exe</span><span class="w"> </span><span class="nx">delete</span><span class="w"> </span><span class="s2">"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Serialize"</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">Startupdelayinmsec</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span></code></pre></div></div>

<p><a href="https://www.elevenforum.com/t/enable-or-disable-delay-of-running-startup-apps-in-windows-11.9144/">Source and more info</a></p>

<h2 id="bonus-microsoft-powertoys">Bonus: Microsoft PowerToys</h2>

<p>I was planning to keep this post to only native Windows features and settings, but I couldn’t resist including Microsoft PowerToys.
PowerToys is kind of Microsoft’s way of quickly experimenting and iterating on features that <em>should</em> be in Windows, but aren’t.
If a feature gets popular enough, it may be included in Windows natively.</p>

<p>Microsoft PowerToys is an application that adds a ton of great features to Windows, and it is being updated all the time.</p>

<p>See the <a href="https://learn.microsoft.com/en-us/windows/powertoys/">Microsoft PowerToys docs</a> for extensive documentation on all of the features, and how to install and configure them.</p>

<p>A few of my favourite features are:</p>

<ul>
  <li>Always On Top: Keep a window always on top of others.</li>
  <li>Find My Mouse: Spotlight the mouse cursor when you press the <kbd>Ctrl</kbd> key twice.</li>
  <li>File Locksmith: Easily unlock files that are in use by other processes.</li>
  <li>Image Resizer: Resize images quickly and easily.</li>
  <li>Color Picker: Get the RGB, HEX, and HSL values of any color on the screen.</li>
</ul>

<p>You can install PowerToys by running the following command in a terminal, such as PowerShell:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget <span class="nb">install</span> <span class="nt">--id</span> Microsoft.PowerToys <span class="nt">--source</span> winget
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>I hope you found some of these settings and features helpful.
Have a favourite or one that I didn’t mention?
Let me know in the comments!</p>

<p>Happy customizing!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Performance" /><category term="Productivity" /><category term="Windows" /><category term="Performance" /><category term="Productivity" /><category term="Windows" /><summary type="html"><![CDATA[Windows has a ton of native settings and features that can make it more pleasant and efficient to use. Below are some of my favorites that I always adjust after a fresh install of Windows. These are all personal preference, and I show the settings I like, but use whatever works best for you.]]></summary></entry><entry><title type="html">Find the assembly version of a DLL file with PowerShell</title><link href="https://blog.danskingdom.com/Find-the-assembly-version-of-a-DLL-file-with-PowerShell/" rel="alternate" type="text/html" title="Find the assembly version of a DLL file with PowerShell" /><published>2025-02-07T00:00:00+00:00</published><updated>2025-02-07T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Find-the-assembly-version-of-a-DLL-file-with-PowerShell</id><content type="html" xml:base="https://blog.danskingdom.com/Find-the-assembly-version-of-a-DLL-file-with-PowerShell/"><![CDATA[<p>Sometimes you cannot find the assembly version of a DLL by simply looking at the file properties in File Explorer.
In this case, you can use PowerShell to find the assembly version of a DLL file.</p>

<h2 id="tldr">TL;DR</h2>

<p>Here’s the PowerShell command to find the assembly version of a DLL file:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">System.Reflection.AssemblyName</span><span class="p">]::</span><span class="n">GetAssemblyName</span><span class="p">(</span><span class="s2">"C:\path\to\your.dll"</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>The object returned will contain many properties, with the assembly <code class="language-plaintext highlighter-rouge">Version</code> and <code class="language-plaintext highlighter-rouge">Name</code> written the to the terminal by default.</p>

<p><img src="/assets/Posts/2025-02-07-Find-the-assembly-version-of-a-DLL-file-with-PowerShell/use-powershell-to-get-assembly-version.png" alt="Using PowerShell to get the assembly version of a DLL file" /></p>

<p>Since we are just calling a .NET method, you could also do this as C# code in a console app.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">assembly</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Reflection</span><span class="p">.</span><span class="n">AssemblyName</span><span class="p">.</span><span class="nf">GetAssemblyName</span><span class="p">(</span><span class="s">"C:\\path\\to\\your.dll"</span><span class="p">);</span>
<span class="n">System</span><span class="p">.</span><span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">assembly</span><span class="p">.</span><span class="n">Version</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="background">Background</h2>

<p>I was having a problem where the version of the <code class="language-plaintext highlighter-rouge">System.Memory.dll</code> assembly being copied to my application artifacts was different than the version referenced by my NuGet packages, causing my application to crash at runtime.
NuGet automatically included an assembly binding redirect in my <code class="language-plaintext highlighter-rouge">app.config</code> file, but the version in the binding redirect was not the same as the version of the assembly being copied into the artifacts.
The assembly binding redirects were referencing version <code class="language-plaintext highlighter-rouge">4.0.2.0</code>, but the version of the assembly being copied into the artifacts was <code class="language-plaintext highlighter-rouge">4.0.1.2</code> (although I didn’t know this yet).</p>

<p>Here is the assembly binding redirect that was automatically added to my <code class="language-plaintext highlighter-rouge">app.config</code> file by NuGet:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;assemblyBinding</span> <span class="na">xmlns=</span><span class="s">"urn:schemas-microsoft-com:asm.v1"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;dependentAssembly&gt;</span>
    <span class="nt">&lt;assemblyIdentity</span> <span class="na">name=</span><span class="s">"System.Memory"</span> <span class="na">publicKeyToken=</span><span class="s">"cc7b13ffcd2ddd51"</span> <span class="na">culture=</span><span class="s">"neutral"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;bindingRedirect</span> <span class="na">oldVersion=</span><span class="s">"0.0.0.0-4.0.2.0"</span> <span class="na">newVersion=</span><span class="s">"4.0.2.0"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/dependentAssembly&gt;</span>
<span class="nt">&lt;/assemblyBinding&gt;</span>
</code></pre></div></div>

<p>Often times you can find the assembly version of a DLL by simply looking at the file properties in File Explorer, under the <code class="language-plaintext highlighter-rouge">Details</code> tab.
However, in this case, neither version of the <code class="language-plaintext highlighter-rouge">System.Memory.dll</code> assemblies was showing their proper assembly version in the file properties.</p>

<p><img src="/assets/Posts/2025-02-07-Find-the-assembly-version-of-a-DLL-file-with-PowerShell/system.memory.dll-file-properties.png" alt="The file properties of both versions of System.Memory.dll" /></p>

<p>You can see that the <code class="language-plaintext highlighter-rouge">File version</code> and <code class="language-plaintext highlighter-rouge">Product version</code> fields of the assemblies in the NuGet package and in the artifact are not showing the actual assembly version of the DLL; either <code class="language-plaintext highlighter-rouge">4.0.2.0</code> or <code class="language-plaintext highlighter-rouge">4.0.1.2</code>.</p>

<p>So I could see that the assembly versions were different, but wasn’t sure what the actual assembly versions were in order to update the assembly binding redirect.</p>

<p>PowerShell to the rescue!</p>

<p><img src="/assets/Posts/2025-02-07-Find-the-assembly-version-of-a-DLL-file-with-PowerShell/use-powershell-to-get-both-assembly-versions.png" alt="Using PowerShell to get the assembly versions of both assemblies" /></p>

<p>Now that I knew the actual assembly versions, I could update the assembly binding redirect in my <code class="language-plaintext highlighter-rouge">app.config</code> file to match the version of the assembly being copied into the artifacts.
Specifically, <code class="language-plaintext highlighter-rouge">newVersion="4.0.1.2</code>.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;assemblyBinding</span> <span class="na">xmlns=</span><span class="s">"urn:schemas-microsoft-com:asm.v1"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;dependentAssembly&gt;</span>
    <span class="nt">&lt;assemblyIdentity</span> <span class="na">name=</span><span class="s">"System.Memory"</span> <span class="na">publicKeyToken=</span><span class="s">"cc7b13ffcd2ddd51"</span> <span class="na">culture=</span><span class="s">"neutral"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;bindingRedirect</span> <span class="na">oldVersion=</span><span class="s">"0.0.0.0-4.0.2.0"</span> <span class="na">newVersion=</span><span class="s">"4.0.1.2"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/dependentAssembly&gt;</span>
<span class="nt">&lt;/assemblyBinding&gt;</span>
</code></pre></div></div>

<p>This fixed the runtime crash and got everything back on track in time for my release.</p>

<h3 id="a-proper-fix">A proper fix</h3>

<p>While updating the assembly binding redirect to use the older version of the assembly fixed the issue, it’s kind of a hacky solution.
If we update the NuGet package to a newer version, NuGet will automatically update the assembly binding redirect version and things will break again.
A better solution would be to find out why the older version of the assembly was being copied into the artifacts and fix it.</p>

<p>I had already confirmed that all projects in the solution were referencing the same version of the <code class="language-plaintext highlighter-rouge">System.Memory</code> NuGet package, so I knew that wasn’t the issue.
So I decided to build my projects and then use PowerShell to find all of the <code class="language-plaintext highlighter-rouge">System.Memory.dll</code> assemblies and report their assembly version.</p>

<p>Here is the PowerShell script I used:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Find all System.Memory.dll assemblies in the repository.</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$directoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\MyProjectRepo'</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$assemblyFilePaths</span><span class="w"> </span><span class="o">=</span><span class="w">
  </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$directoryPath</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s1">'System.Memory.dll'</span><span class="w"> </span><span class="o">|</span><span class="w">
  </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-ExpandProperty</span><span class="w"> </span><span class="nx">FullName</span><span class="w">

</span><span class="c"># Get the assembly version of each assembly, and add the file path to the object.</span><span class="w">
</span><span class="nv">$assemblies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@()</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$filePath</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$assemblyFilePaths</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nv">$assembly</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.Reflection.AssemblyName</span><span class="p">]::</span><span class="n">GetAssemblyName</span><span class="p">(</span><span class="nv">$filePath</span><span class="p">)</span><span class="w">
  </span><span class="nv">$assembly</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Add-Member</span><span class="w"> </span><span class="nt">-MemberType</span><span class="w"> </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">FilePath</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$filePath</span><span class="w">
  </span><span class="nv">$assemblies</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nv">$assembly</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Output the assembly versions and file paths, sorted by version then file path.</span><span class="w">
</span><span class="nv">$assemblies</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Version</span><span class="p">,</span><span class="w"> </span><span class="nx">FilePath</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Sort-Object</span><span class="w"> </span><span class="nx">Version</span><span class="p">,</span><span class="w"> </span><span class="nx">FilePath</span><span class="w">
</span></code></pre></div></div>

<p>The output of the script looked something like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Version FilePath
------- --------
4.0.1.2 C:\MyProjectRepo\Project1.Tests\bin\Debug\System.Memory.dll
4.0.1.2 C:\MyProjectRepo\Project2.Tests\bin\Debug\System.Memory.dll
4.0.1.2 C:\MyProjectRepo\Project3.Tests\bin\Debug\System.Memory.dll
...
4.0.2.0 C:\MyProjectRepo\Project1\bin\Debug\System.Memory.dll
4.0.2.0 C:\MyProjectRepo\Project2\bin\Debug\System.Memory.dll
4.0.2.0 C:\MyProjectRepo\Project3\bin\Debug\System.Memory.dll
...
</code></pre></div></div>

<p>You can see that all of the test projects were bringing in the older version of the assembly, while the main projects were bringing in the newer version.
The test projects did not reference the <code class="language-plaintext highlighter-rouge">System.Memory</code> NuGet package, so when they needed the <code class="language-plaintext highlighter-rouge">System.Memory.dll</code> assembly they must have just been copying the older version from the GAC (Global Assembly Cache).
Then somehow the assembly from one of the test projects was being copied into the artifacts; unfortunately, this particular solution has a non-standard build process.</p>

<p>The solution was to add a reference to the <code class="language-plaintext highlighter-rouge">System.Memory</code> NuGet package to each of the test projects that needed it so they would bring in the correct version of the assembly.
I then reverted the change I had made to the assembly binding redirect in the <code class="language-plaintext highlighter-rouge">app.config</code> file, and now it can be automatically managed by NuGet again.</p>

<h2 id="conclusion">Conclusion</h2>

<p>If you came here just wanting to know how to get the assembly version of a DLL file, I hope the TL;DR got you going quickly.
If you read the entire post, I hope you found the background information helpful and maybe learned something new.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="DotNet" /><category term="PowerShell" /><category term="DotNet" /><category term="PowerShell" /><summary type="html"><![CDATA[Sometimes you cannot find the assembly version of a DLL by simply looking at the file properties in File Explorer. In this case, you can use PowerShell to find the assembly version of a DLL file.]]></summary></entry><entry><title type="html">Prevent NuGet package vulnerabilities from breaking the build</title><link href="https://blog.danskingdom.com/Prevent-NuGet-package-vulnerabilities-from-breaking-the-build/" rel="alternate" type="text/html" title="Prevent NuGet package vulnerabilities from breaking the build" /><published>2024-11-16T00:00:00+00:00</published><updated>2024-11-16T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Prevent-NuGet-package-vulnerabilities-from-breaking-the-build</id><content type="html" xml:base="https://blog.danskingdom.com/Prevent-NuGet-package-vulnerabilities-from-breaking-the-build/"><![CDATA[<p><strong>TL;DR</strong>: If you enable “Treat warnings as errors” in your .NET projects, NuGet audit warnings for security vulnerabilities may break your build, but there are ways to work around it if needed.</p>

<p>Visual Studio 2022 v17.8 and NuGet 6.8 introduced a wonderful security feature that will generate warnings for NuGet packages that your projects reference that have known security vulnerabilities.
Visual Studio v17.12 and NuGet v6.12 took this further by also generating warnings for vulnerable transitive dependencies those packages depend on (<a href="https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-notes#net">see the VS release notes</a>).
This gives developers better insight into potential security risks in their software, and can prompt them to update NuGet packages to a more secure version.
Read more about <a href="https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages">the NuGet audit features on the MS docs here</a>.</p>

<p>Here’s an example of a NuGet audit warning message for a package with a known vulnerability:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NU1904: Warning As Error: Package 'log4net' 2.0.8 has a known critical severity vulnerability, https://github.com/advisories/GHSA-2cwj-8chv-9pp9
</code></pre></div></div>

<h2 id="the-problem">The problem</h2>

<p>NuGet generating warnings for security vulnerabilities in direct and transitive dependencies is great, but it can have an unintended side-effect of breaking the build if your projects are configured to treat warnings as errors.
Treating warnings as errors is not on by default, but enabling it is a common practice to help ensure code quality and security.</p>

<p>This new NuGet audit feature means that if you have <code class="language-plaintext highlighter-rouge">TreatWarningsAsErrors</code> enabled, your code may no longer compile, even if you haven’t changed the code.
Some examples of when this could happen include:</p>

<ul>
  <li>You update Visual Studio / NuGet on your local machine to a version that generates new vulnerability warnings, so you can no longer build the code locally.</li>
  <li>Your build server updates Visual Studio / NuGet, so the build server can no longer build the code.
Hosted platforms like GitHub and Azure DevOps agents often install new versions without any notice.</li>
  <li>A vulnerability is discovered in a NuGet package that your project uses and it is added to <a href="https://github.com/advisories/">https://github.com/advisories/</a>.</li>
</ul>

<p>A common problem developers encounter sooner or later is when code compiles one day, but not the next, even though you haven’t changed the code.
It can be very frustrating.</p>

<h2 id="the-solutions">The solutions</h2>

<p>There are a few ways to work around the problem of NuGet audit warnings breaking the build.</p>

<p>The best solution is to:</p>

<ul>
  <li>Update your NuGet package reference to a version that doesn’t have a vulnerability.</li>
</ul>

<p>While this is the simplest and best solution, it may not always be possible.
A few reasons why include:</p>

<ul>
  <li>The latest version of the NuGet package (or one of its dependencies) has a vulnerability.</li>
  <li>The NuGet package is no longer maintained, so there is no new version to update to.</li>
  <li>Newer versions of the NuGet package (or one of its dependencies) have a breaking change that would require significant code changes, and you do not have the time or resources to dedicate to making and testing those changes right now.</li>
</ul>

<h3 id="when-updating-the-nuget-package-is-not-a-viable-solution">When updating the NuGet package is not a viable solution</h3>

<p>If updating the NuGet package is not possible, there are a few other solutions you can try.
These are listed in order of most recommended to least recommended:</p>

<ul>
  <li><strong>Do not treat NuGet audit warnings as errors by using WarningsNotAsErrors</strong>: This is the best solution, as it will allow your code to compile, and you will still be notified of the security vulnerabilities as a warning.
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/errors-warnings#warningsaserrors-and-warningsnotaserrors">See the MS docs here for how to enable or disable it</a>
    <ul>
      <li>
        <p>Add the following to your project file (.csproj) to treat the NuGet audit warnings as warnings instead of errors:</p>

        <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;WarningsNotAsErrors&gt;</span>NU1901,NU1902,NU1903,NU1904<span class="nt">&lt;/WarningsNotAsErrors&gt;</span>
</code></pre></div>        </div>

        <p>These are the 3 NuGet audit warning codes that I encountered, but there may be others.</p>

        <p>If you are editing the .csproj file by hand, you will want to add it to the <code class="language-plaintext highlighter-rouge">&lt;PropertyGroup&gt;</code> section of both your <code class="language-plaintext highlighter-rouge">Debug</code> and <code class="language-plaintext highlighter-rouge">Release</code> configurations.</p>

        <p>Here is a screenshot of modifying the project properties in Visual Studio to enable “Treat warnings as errors” and prevent NuGet audit warnings from being treated as errors (I forgot to add NU1901 in the screenshot):</p>

        <p><img src="/assets/Posts/2024-11-16-Prevent-NuGet-package-vulnerabilities-from-breaking-the-build/enable-treat-warnings-as-errors-and-ignore-nuget-audit-warnings-in-visual-studio.png" alt="Screenshot of modifying the .csproj file in Visual Studio" /></p>
      </li>
    </ul>
  </li>
  <li>
    <p><strong>Suppress specific NuGet audit advisories</strong>: The next best solution is to ignore just the specific NuGet package advisory that you are not able to workaround.
A potential downside is that because you will no longer get a warning, you may forget the package has a vulnerability.
Here is an example of the code to add to your .csproj file to exclude the specific log4net advisory mentioned earlier:</p>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">"Microsoft.NET.Sdk"</span><span class="nt">&gt;</span>
  <span class="c">&lt;!--  other parts of the project left out of this example --&gt;</span>
  <span class="nt">&lt;ItemGroup&gt;</span>
    <span class="nt">&lt;NuGetAuditSuppress</span> <span class="na">Include=</span><span class="s">"https://github.com/advisories/GHSA-2cwj-8chv-9pp9"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/ItemGroup&gt;</span>
<span class="nt">&lt;/Project&gt;</span>
</code></pre></div>    </div>

    <p>See <a href="https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages#excluding-advisories">the NuGet audit docs</a> for more information.</p>
  </li>
  <li><strong>Disable TreatWarningsAsErrors</strong>: This is not ideal, as it will prevent you from being <em>forced</em> to fix code quality issues and security vulnerabilities, which some people and companies prefer.
<a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/errors-warnings#treatwarningsaserrors">See the MS docs for how to enable or disable it</a></li>
  <li><strong>Disable the NuGet audit feature</strong>: This is not recommended, as it will prevent you from being notified of security vulnerabilities.
<a href="https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages#configuring-nuget-audit">See the MS docs for how to disable it, or have it ignore transitive dependencies</a>.</li>
</ul>

<p>All of these solutions require making changes to the project file (.csproj).
If you only have a few offending projects, updating each project file isn’t too troublesome.
If you have 10s or 100s of projects though, it can be a pain to update them all.</p>

<p>A better solution is to leverage a <code class="language-plaintext highlighter-rouge">Directory.Build.props</code> file to apply the changes to all projects in a directory.
You can read more about Directory.Build.props and how to use it <a href="https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory">on the MS docs here</a>.</p>

<p><a href="https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/9.0/nugetaudit-transitive-packages">This MS doc</a> has more information on some of the workarounds mentioned above.</p>

<p>You can find more information about potential actions to take when you encounter a vulnerable dependency in <a href="https://learn.microsoft.com/en-us/nuget/concepts/auditing-packages#actions-when-packages-with-known-vulnerabilities-are-reported">the NuGet audits docs here</a>, and <a href="https://devblogs.microsoft.com/nuget/nugetaudit-2-0-elevating-security-and-trust-in-package-management/">this MS blog post</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The NuGet audit feature is a great addition to help developers be aware of security vulnerabilities in their projects.
However, it can have the unintended side-effect of breaking the build if you treat warnings as errors.
By updating your NuGet packages to versions that do not have vulnerabilities, or by using one of the workarounds mentioned above, you can prevent the build from breaking and still be notified of security vulnerabilities in your projects.</p>

<p>I hope this article helps you keep your code compiling and secure.
If you have any questions or comments, please leave them below.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="DotNet" /><category term="NuGet" /><category term="Visual Studio" /><category term="Should all" /><category term="Start with" /><category term="Capitals" /><summary type="html"><![CDATA[TL;DR: If you enable “Treat warnings as errors” in your .NET projects, NuGet audit warnings for security vulnerabilities may break your build, but there are ways to work around it if needed.]]></summary></entry><entry><title type="html">Tech podcasts I enjoy</title><link href="https://blog.danskingdom.com/Tech-podcasts-I-enjoy/" rel="alternate" type="text/html" title="Tech podcasts I enjoy" /><published>2024-11-11T00:00:00+00:00</published><updated>2025-06-27T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Tech-podcasts-I-enjoy</id><content type="html" xml:base="https://blog.danskingdom.com/Tech-podcasts-I-enjoy/"><![CDATA[<p>I started listening to podcasts in the summer of 2023, and have been enjoying them while walking my dogs or exercising ever since.
I have found them to be a great way to keep up-to-date with the latest trends in the tech industry, and to get perspectives on the softer side of the tech industry, such as career advice and hearing other software developers’ stories of working at other companies, both large and small.</p>

<p><img src="/assets/Posts/2024-11-11-Tech-podcasts-I-enjoy/cartoon-software-developer-walking-dogs-while-listening-to-podcast.jpg" alt="Cartoon software developer walking dogs while listening to a podcast" /></p>

<p>Below are some of the podcasts I enjoy, typically on Spotify at 1.8x speed, and why I like them.</p>

<h2 id="technical-podcasts">Technical podcasts</h2>

<p>I’m primarily a .NET developer, so the technical podcasts I listen to are usually .NET or Azure focused.
Here are some technical podcasts that are great for software developers and IT pros:</p>

<ul>
  <li><a href="https://azuredevopspodcast.clear-measure.com">Azure DevOps Podcast</a>: This podcast is hosted by Jeffrey Palermo, and is a great way to keep up-to-date with the latest .NET and Azure features and technology.
He interviews a wide range of guests, such as Microsoft employees and MVPs, .NET-related authors, and other software developers in the industry, some of which have several decades of experience.</li>
  <li><a href="https://powershellpodcast.podbean.com/">The PowerShell Podcast</a>: This podcast is hosted by Andrew Pla, and features guests from the PowerShell community.
This podcast is a great way to stay up-to-date with what is happening in the PowerShell world, hear from others who use PowerShell regularly, and learn about new modules and tools that can help make IT pros and developers more productive.</li>
  <li><a href="https://dotnetrocks.com">.NET Rocks</a>: This podcast is hosted by Carl Franklin and Richard Campbell, and they interview guests from the .NET community.
Carl and Richard have been podcasting for over 20 years, and have a wealth of knowledge and experience in the tech industry.
They have interviewed many of the top names in the .NET community, and have a large back catalog of episodes that you can listen to.</li>
  <li><a href="https://dotnetcore.show">The Modern .NET Show</a>: This podcast is hosted by Jamie Taylor, and he interviews guests from the .NET community.
Jamie’s podcast is a great way to stay up-to-date with the latest .NET features and technology, and hear from others who are using .NET in their day-to-day work.</li>
  <li><a href="https://www.runasradio.com/">Run As Radio</a>: This podcast is hosted by Richard Campbell and has nearly 1000 episodes, with a new one each week.
He interviews guests from the IT community on a wide range of topics, and is a great way to keep up-to-date with the latest trends and tools in the IT industry.</li>
</ul>

<p>Some of these podcasts often provide discount codes for upcoming conferences, which can save you hundreds of dollars off registration.</p>

<h2 id="non-technical-podcasts">Non-technical podcasts</h2>

<p>This is a list of non-technical podcasts that your spouse or even your kids might enjoy, where you don’t need to be a software developer or IT pro to understand what they are talking about or find it interesting:</p>

<p>Interesting true stories:</p>

<ul>
  <li><a href="https://corecursive.com">Corecursive</a>: This podcast is hosted by Adam Gordon Bell.
Adam will either have a guest on the show to tell their story, or sometimes he will just tell someone’s story himself, of doing something notable in the tech industry, such as creating the JSON spec, porting Doom to a gaming console, or shadow-working on the Apple campus without them knowing or getting paid in hopes of shipping their software with the Mac OS.
I really like this podcast, as it is essentially a collection of interesting true stories about people who have done something in the tech industry.
The stories may have happened 50 years ago or 5 years ago, and they are all interesting and well told.
It is a great way to hear stories from other software developers and get a history of what has changed over the years, and to learn about the different paths people have taken to get into the tech industry.</li>
  <li><a href="https://darknetdiaries.com">Darknet Diaries</a>: This podcast is hosted by Jack Rhysider, and is a collection of true stories about hackers, malware, and cyber security.
Like the Corecursive podcast, it focuses on telling true stories, but about anything related to cyber security or the darker side of the web, such as the story of the guy who hacked the FBI, or the judge who spent months/years posing online as a terrorist in order to gain intel and alert the authorities before a terrorist plan could be executed.</li>
  <li><a href="https://www.youtube.com/@pragmaticengineer">The Pragmatic Engineer</a>: This podcast is hosted by Gergely Orosz.
Gergely typically interviews a guest on the show where they talk about the history of a company, product, or technology, such as how Microsoft evolved over the years from the 1980s to today, or how GitHub got started and has grown and changed over time.</li>
</ul>

<p>Level-up your engineering soft skills:</p>

<ul>
  <li><a href="https://softskills.audio">Soft Skills Engineering</a>: This podcast is hosted by Dave Smith and Jamison Dance, and is a great way to get career advice and hear stories from other software developers.
The hosts read questions submitted by listeners, such as how to ask for a promotion, deal with a difficult coworker, or get your voice heard in the company, and then give their advice on how to handle the situation.
You can submit your own questions for them on their website.</li>
  <li><a href="https://www.developingup.com">Developing Up</a>: This podcast is no longer recoding new episodes, but still has a lot of great advice nonetheless.
Like the Soft Skills Engineering podcast, Developing Up discusses many of the processes around being a software engineer (code reviews, pair programming, security, working remotely, career advice) that remain true over time.</li>
</ul>

<p>Other podcasts:</p>

<ul>
  <li><a href="https://hanselminutes.com">Hanselminutes</a>: This podcast is hosted by Scott Hanselman, and features guests from the tech and other industries.
Sometimes Scott’s podcasts are very technical, and other times not at all.
Compared to the other podcasts on this list, I would say Scott’s podcast is a variety show, and you’re never quite sure what you’re going to get.
Due to the nature of so many different topics, I find some episodes more interesting than others.</li>
  <li><a href="https://www.compromisingpositions.co.uk">Compromising Positions</a>: This podcast is hosted by Lianne Potter and Jeff Watkins, and is all about cyber security.
They typically interview a non-technical guest on their show and discuss how security could be improved in their area.
I personally find this podcast hit or miss; I’ve really enjoyed some episodes, while others left me zoning out or feeling bored.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>I plan to add to this list over time as I discover more great tech-related podcasts, so check back later for more recommendations.</p>

<p>For now, I hope you find this list helpful and are able to discover a new podcast that you love.</p>

<p>Happy listening! 🎧</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Social" /><category term="PowerShell" /><category term="Social" /><category term="Podcast" /><summary type="html"><![CDATA[I started listening to podcasts in the summer of 2023, and have been enjoying them while walking my dogs or exercising ever since. I have found them to be a great way to keep up-to-date with the latest trends in the tech industry, and to get perspectives on the softer side of the tech industry, such as career advice and hearing other software developers’ stories of working at other companies, both large and small.]]></summary></entry><entry><title type="html">Updating from System.Data.SqlClient to Microsoft.Data.SqlClient</title><link href="https://blog.danskingdom.com/Updating-from-System-Data-SqlClient-to-Microsoft-Data-SqlClient/" rel="alternate" type="text/html" title="Updating from System.Data.SqlClient to Microsoft.Data.SqlClient" /><published>2024-10-29T00:00:00+00:00</published><updated>2024-11-04T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Updating-from-System-Data-SqlClient-to-Microsoft-Data-SqlClient</id><content type="html" xml:base="https://blog.danskingdom.com/Updating-from-System-Data-SqlClient-to-Microsoft-Data-SqlClient/"><![CDATA[<p>Over the past couple days I’ve been updating a .NET 8 solution from using <code class="language-plaintext highlighter-rouge">System.Data.SqlClient</code> to <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code>.
The reason for this, besides the fact that <a href="https://techcommunity.microsoft.com/t5/sql-server-blog/announcement-system-data-sqlclient-package-is-now-deprecated/ba-p/4227205">the System.Data.SqlClient NuGet package is now deprecated</a>, is that I wanted to see SQL performance counter metrics, such as how many active SQL connections my app was using.
<code class="language-plaintext highlighter-rouge">System.Data.SqlClient</code> supports performance counters on .NET Framework apps, but not .NET Core apps.
To <a href="https://learn.microsoft.com/en-us/sql/connect/ado-net/event-counters">get SqlClient metrics in .NET Core apps</a>, you need to use <a href="https://www.nuget.org/packages/Microsoft.Data.SqlClient">the <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code> NuGet package</a>.</p>

<blockquote>
  <p>NOTE: Only the <code class="language-plaintext highlighter-rouge">System.Data.SqlClient</code> NuGet package is deprecated, not the <code class="language-plaintext highlighter-rouge">System.Data.SqlClient</code> namespace in .NET Framework.
This means it only affects .NET Core apps, not .NET Framework apps.
That said, .NET Framework apps can still benefit from using <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code> instead, as that’s where development is happening for new features and performance improvements.</p>
</blockquote>

<h2 id="tldr">TL;DR</h2>

<p>The SqlClient GitHub repo has a <a href="https://github.com/dotnet/SqlClient/blob/main/porting-cheat-sheet.md">migration cheat sheet</a> for everything that you will need to change when migrating from <code class="language-plaintext highlighter-rouge">System.Data.SqlClient</code> to <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code>.</p>

<p>The team also plans to automate much of the migration process with the <a href="https://dotnet.microsoft.com/en-us/platform/upgrade-assistant">.NET Upgrade Assistant</a> in the future, so keep an eye out for that.</p>

<h2 id="my-journey">My journey</h2>

<p><a href="https://devblogs.microsoft.com/dotnet/introducing-the-new-microsoftdatasqlclient/">Microsoft.Data.SqlClient was introduced in 2019</a>, and skimming <a href="https://github.com/dotnet/SqlClient/issues/2778">the GitHub migration guide issue</a> said to simply:</p>

<ul>
  <li>Add the <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code> NuGet package to your project and</li>
  <li>Replace <code class="language-plaintext highlighter-rouge">using System.Data.SqlClient;</code> with <code class="language-plaintext highlighter-rouge">using Microsoft.Data.SqlClient;</code></li>
</ul>

<p>That seemed simple enough.</p>

<p>So I did that, and everything compiled fine.
Nice! 💪</p>

<p>However, I then ran the integration tests and saw a lot of failures 😭.</p>

<blockquote>
  <p>NOTE: Be weary of runtime issues after migrating.
Be sure to test your application.</p>
</blockquote>

<h3 id="issue-1-connection-error">Issue 1: Connection error</h3>

<p>The first error I encountered was an issue connecting to the SQL database:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A connection was successfully established with the server, but then an error occurred during the login process.
(provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code> is meant to be a backward compatible improvement over <code class="language-plaintext highlighter-rouge">System.Data.SqlClient</code>, and that includes being more secure.
All connections are now encrypted by default, and the server’s certificate must be trusted.
If you are connecting to a SQL Server that is using a self-signed certificate, you will need to add the certificate to the trusted root certificate store on the machine running the application, or (not recommended) adjust the connection string to either use <code class="language-plaintext highlighter-rouge">Encrypt=False</code> or <code class="language-plaintext highlighter-rouge">TrustServerCertificate=True</code>.</p>

<h2 id="issue-2-additional-namespaces-to-change">Issue 2: Additional namespaces to change</h2>

<p>The next error I ran into was:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed to convert parameter value from a SqlDataRecord[] to a IEnumerable`1.
unhandled_exception_InvalidCastException
</code></pre></div></div>

<p>After a few hours of unravelling and debugging the app, I came across <a href="https://github.com/dotnet/SqlClient/issues/323#issuecomment-556775371">this comment on a GitHub issue</a> mentioning that in addition to updating the <code class="language-plaintext highlighter-rouge">using System.Data.SqlClient;</code> statements, I also needed to update the <code class="language-plaintext highlighter-rouge">using Microsoft.SqlServer.Server;</code> statements to <code class="language-plaintext highlighter-rouge">using Microsoft.Data.SqlClient.Server;</code>.
I also found <a href="https://stackoverflow.com/a/61713249/602585">this Stack Overflow answer</a> that mentioned the same thing.</p>

<h3 id="issue-3-connection-string-formatting">Issue 3: Connection string formatting</h3>

<p>The final problem I ran into was some of the unit tests compared the SQL connection string to ensure it had all of the expected properties and values.
It seems that the <code class="language-plaintext highlighter-rouge">SqlConnectionStringBuilder.ConnectionString</code> property in <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code> changed the formatting to use spaces in the connection string properties, so <code class="language-plaintext highlighter-rouge">ApplicationIntent</code> becomes <code class="language-plaintext highlighter-rouge">Application Intent</code>, and <code class="language-plaintext highlighter-rouge">MultiSubnetFailover</code> becomes <code class="language-plaintext highlighter-rouge">Multi Subnet Failover</code>.
It’s a breaking change that most likely won’t affect your app, unless your app is used to build or return connection strings for other apps.</p>

<p>The problem is that <a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlconnection.connectionstring">the System.Data.SqlClient Connection String</a> does not support the spaces, but <a href="https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.connectionstring">the Microsoft.Data.SqlClient Connection String</a> does.
So if an app using <code class="language-plaintext highlighter-rouge">System.Data.SqlClient</code> tries to connect using a connection string provided by <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code>, it will fail with an error message like the following:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Keyword not supported: 'application intent'
</code></pre></div></div>

<p>Your options here are to either update all of your apps to use <code class="language-plaintext highlighter-rouge">Microsoft.Data.SqlClient</code>, or put code in place to ensure spaces are removed from the connection string keywords.</p>

<p>I opened <a href="https://github.com/dotnet/SqlClient/issues/2974">this GitHub issue</a> to see if MS will consider using the backward-compatible keywords, and created <a href="https://github.com/dotnet/SqlClient/pull/2975">this PR</a> to update the migration cheat sheet (mentioned below) with this problem.</p>

<h2 id="conclusion">Conclusion</h2>

<p>After making these changes, everything worked as expected 🙌.</p>

<p>Later I noticed I had overlooked <a href="https://github.com/dotnet/SqlClient/blob/main/porting-cheat-sheet.md">this migration cheat sheet</a> that was linked to from the deprecation announcement page 🤦‍♂️.
It includes the first two changes I mentioned above, as well as a few others that I didn’t run into.
The deprecation announcement page also mentions they plan to update <a href="https://dotnet.microsoft.com/en-us/platform/upgrade-assistant">the .NET Upgrade Assistant</a> to help with this migration in the future, so by the time you read this, you may be able to use that tool.</p>

<p>If you need to migrate your apps, I hope you find this useful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="DotNet" /><category term="CSharp" /><category term="SQL" /><category term="DotNet" /><category term="CSharp" /><category term="SQL" /><summary type="html"><![CDATA[Over the past couple days I’ve been updating a .NET 8 solution from using System.Data.SqlClient to Microsoft.Data.SqlClient. The reason for this, besides the fact that the System.Data.SqlClient NuGet package is now deprecated, is that I wanted to see SQL performance counter metrics, such as how many active SQL connections my app was using. System.Data.SqlClient supports performance counters on .NET Framework apps, but not .NET Core apps. To get SqlClient metrics in .NET Core apps, you need to use the Microsoft.Data.SqlClient NuGet package.]]></summary></entry><entry><title type="html">Type less by using PowerShell type accelerators</title><link href="https://blog.danskingdom.com/Type-less-by-using-PowerShell-type-accelerators/" rel="alternate" type="text/html" title="Type less by using PowerShell type accelerators" /><published>2024-10-14T00:00:00+00:00</published><updated>2024-10-14T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Type-less-by-using-PowerShell-type-accelerators</id><content type="html" xml:base="https://blog.danskingdom.com/Type-less-by-using-PowerShell-type-accelerators/"><![CDATA[<p>I had heard the PowerShell term “type accelerators” a few times, but never really knew what that meant.
I finally decided to check out <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_type_accelerators">the about_Type_Accelerators docs</a> and it turns out they’re simply aliases for .NET types.</p>

<p>Just like you might define an alias for a function, you can define an alias for a .NET class or enum type, only it’s called a “type accelerator”.
It turns out that I (and probably you) had been using them all along without realizing it.</p>

<h2 id="built-in-type-accelerators">Built-in type accelerators</h2>

<p>Here are some common built-in type accelerators:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Int32]</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.String]</span><span class="w">
</span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Boolean]</span><span class="w">
</span><span class="p">[</span><span class="n">datetime</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.DateTime]</span><span class="w">
</span><span class="p">[</span><span class="n">array</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Array]</span><span class="w">
</span><span class="p">[</span><span class="n">hashtable</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Collections.Hashtable]</span><span class="w">
</span><span class="p">[</span><span class="n">xml</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Xml.XmlDocument]</span><span class="w">
</span><span class="p">[</span><span class="n">regex</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Text.RegularExpressions.Regex]</span><span class="w">
</span><span class="p">[</span><span class="n">pscredential</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Management.Automation.PSCredential]</span><span class="w">
</span><span class="p">[</span><span class="n">pscustomobject</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Management.Automation.PSCustomObject]</span><span class="w">
</span><span class="p">[</span><span class="n">ipaddress</span><span class="p">]</span><span class="w"> </span><span class="c"># Instead of [System.Net.IPAddress]</span><span class="w">
</span></code></pre></div></div>

<p>So the following two lines are equivalent:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$myInt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span><span class="p">[</span><span class="n">System.Int32</span><span class="p">]</span><span class="w"> </span><span class="nv">$myInt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span></code></pre></div></div>

<p>And these two lines are equivalent:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrWhitespace</span><span class="p">(</span><span class="nv">$myString</span><span class="p">)</span><span class="w">
</span><span class="p">[</span><span class="n">System.String</span><span class="p">]::</span><span class="n">IsNullOrWhitespace</span><span class="p">(</span><span class="nv">$myString</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>And these two lines are equivalent:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">pscredential</span><span class="p">]</span><span class="w"> </span><span class="nv">$myCred</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Credential</span><span class="w">
</span><span class="p">[</span><span class="n">System.Management.Automation.PSCredential</span><span class="p">]</span><span class="w"> </span><span class="nv">$myCred</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Credential</span><span class="w">
</span></code></pre></div></div>

<p>And these two lines are equivalent:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">pscustomobject</span><span class="p">]</span><span class="w"> </span><span class="nv">$myObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'John'</span><span class="p">;</span><span class="w"> </span><span class="nx">Age</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">30</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">[</span><span class="n">System.Management.Automation.PSCustomObject</span><span class="p">]</span><span class="w"> </span><span class="nv">$myObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'John'</span><span class="p">;</span><span class="w"> </span><span class="nx">Age</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">30</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Basically, type accelerators are just an alias for a fully qualified .NET type name.
The first two examples don’t save too many keystrokes, but ones like <code class="language-plaintext highlighter-rouge">[pscredential]</code> instead of remembering <code class="language-plaintext highlighter-rouge">System.Management.Automation.PSCredential</code> can be a real time saver.</p>

<p>Before understanding that they are simply an alias, I would do a Google lookup for the full type name every time I needed to use a non-common one, to make sure it would work on not only my machine, but others’ as well.
Knowing that they are aliases built into the language gives me confidence to use them in my scripts, regardless of the machine they’re run on.</p>

<p>Be sure to check out <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_type_accelerators">the full list of built-in type accelerators in the MS docs</a>, as there are quite a few.</p>

<h2 id="get-all-type-accelerators">Get all type accelerators</h2>

<p>You can get a list of all of the built-in type accelerators for your PowerShell version by running the following command:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">PSObject</span><span class="p">]</span><span class="o">.</span><span class="nf">Assembly</span><span class="o">.</span><span class="nf">GetType</span><span class="p">(</span><span class="s1">'System.Management.Automation.TypeAccelerators'</span><span class="p">)::</span><span class="n">Get</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2024-10-14-Type-less-by-using-PowerShell-type-accelerators/PowerShell-get-all-type-accelerators.png" alt="Get all type accelerators" /></p>

<p>This is similar to running <code class="language-plaintext highlighter-rouge">Get-Alias</code> to get a list of all cmdlet aliases, only these are for class/enum types.</p>

<h2 id="custom-type-accelerators">Custom type accelerators</h2>

<p>You can also create your own type accelerators, whether for your own classes/enums, or for .NET classes that don’t have built-in aliases.</p>

<p>Here’s an example of creating a type accelerator for the <code class="language-plaintext highlighter-rouge">System.Net.Http.HttpClient</code> class:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$accelerator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">PSObject</span><span class="p">]</span><span class="o">.</span><span class="nf">Assembly</span><span class="o">.</span><span class="nf">GetType</span><span class="p">(</span><span class="s1">'System.Management.Automation.TypeAccelerators'</span><span class="p">)</span><span class="w">
</span><span class="nv">$accelerator</span><span class="p">::</span><span class="n">Add</span><span class="p">(</span><span class="s1">'HttpClient'</span><span class="p">,</span><span class="w"> </span><span class="s1">'System.Net.Http.HttpClient'</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>Now you can use <code class="language-plaintext highlighter-rouge">[HttpClient]</code> instead of <code class="language-plaintext highlighter-rouge">[System.Net.Http.HttpClient]</code> in your scripts, like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">HttpClient</span><span class="p">]</span><span class="w"> </span><span class="nv">$client</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">HttpClient</span><span class="w">
</span></code></pre></div></div>

<p>Beware that custom type accelerators only apply if they’re included in the script or module that defines them; they are not available globally like the built-in ones.
This means that if you defined a custom type accelerator in one script, it may not be available in another script.
Similarly, if you define a custom type accelerator in your <code class="language-plaintext highlighter-rouge">$Profile</code>, it will be available on your local machine, but may not be available if the script is run on a different machine.</p>

<h2 id="type-accelerators-in-modules">Type accelerators in modules</h2>

<p>If you’re creating a module and want to include a custom type accelerator for your module’s class/enum so it can be easily referenced, you can include the type accelerator definition in your module’s <code class="language-plaintext highlighter-rouge">.psm1</code> file.
See <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes#exporting-classes-with-type-accelerators">the MS documentation</a> for more details.</p>

<h2 id="using-namespaces-instead-of-type-accelerators">Using namespaces instead of type accelerators</h2>

<p>Rather than creating a bunch of custom type accelerators for .NET types that don’t have built-in ones, you can instead use the <code class="language-plaintext highlighter-rouge">using namespace</code> statement to import an entire namespace and then use the types in that namespace without needing the fully qualified name.</p>

<p>Here’s an example of using the <code class="language-plaintext highlighter-rouge">using namespace</code> statement to import the <code class="language-plaintext highlighter-rouge">System.Net.Http</code> namespace and then using the <code class="language-plaintext highlighter-rouge">HttpClient</code> and <code class="language-plaintext highlighter-rouge">HttpRequestHeaders</code> classes without the fully qualified name:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">System.Net.Http</span><span class="w">

</span><span class="nv">$myHttpClient</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">HttpClient</span><span class="p">]::</span><span class="n">new</span><span class="p">()</span><span class="w"> </span><span class="c"># Instead of [System.Net.Http.HttpClient]::new()</span><span class="w">
</span><span class="nv">$headers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">HttpRequestHeaders</span><span class="p">]::</span><span class="n">new</span><span class="p">()</span><span class="w"> </span><span class="c"># Instead of [System.Net.Http.Headers.HttpRequestHeaders]::new()</span><span class="w">
</span></code></pre></div></div>

<p>A common one that I find myself using is:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">System.IO</span><span class="w">

</span><span class="nv">$myFileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">File</span><span class="p">]::</span><span class="n">ReadAllText</span><span class="p">(</span><span class="s1">'C:\path\to\file.txt'</span><span class="p">)</span><span class="w"> </span><span class="c"># Instead of [System.IO.File]::ReadAllText('C:\path\to\file.txt')</span><span class="w">
</span></code></pre></div></div>

<p>This is a great alternative to using .NET types without having to type out their fully qualified name every time, or define a custom type accelerator for them.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Type accelerators are a great way to save keystrokes.
There are built-in aliases for many .NET types, and you can create your own custom type accelerators as well.
If you find yourself using a .NET type often, consider using it’s type accelerator, or including a <code class="language-plaintext highlighter-rouge">using namespace</code> statement to import it’s namespace.
Just remember that if you include a custom type accelerator or namespace in your <code class="language-plaintext highlighter-rouge">$Profile</code>, it may not be available on other machines.
For more information and examples of using type accelerators, check out <a href="https://4sysops.com/archives/using-powershell-type-accelerators/">this awesome blog post by 4syspos</a>.</p>

<p>Happy scripting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Productivity" /><category term="PowerShell" /><category term="Productivity" /><summary type="html"><![CDATA[I had heard the PowerShell term “type accelerators” a few times, but never really knew what that meant. I finally decided to check out the about_Type_Accelerators docs and it turns out they’re simply aliases for .NET types.]]></summary></entry><entry><title type="html">Visual Studio has a built-in EditorConfig editor</title><link href="https://blog.danskingdom.com/Visual-Studio-has-a-built-in-EditorConfig-editor/" rel="alternate" type="text/html" title="Visual Studio has a built-in EditorConfig editor" /><published>2024-09-20T00:00:00+00:00</published><updated>2024-09-20T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Visual-Studio-has-a-built-in-EditorConfig-editor</id><content type="html" xml:base="https://blog.danskingdom.com/Visual-Studio-has-a-built-in-EditorConfig-editor/"><![CDATA[<p>Somehow I missed it, but for years Visual Studio has had a built-in EditorConfig editor.
I would typically edit my .editorconfig file in VS Code using just text, but Visual Studio’s editor provides a nice UI for it so you don’t have to remember all the setting names.</p>

<p><img src="/assets/Posts/2024-09-20-Visual-Studio-has-a-built-in-EditorConfig-editor/Visual-Studio-EditorConfig-editor-whitespace.png" alt="Visual Studio EditorConfig editor for whitespace settings" /></p>

<p><img src="/assets/Posts/2024-09-20-Visual-Studio-has-a-built-in-EditorConfig-editor/Visual-Studio-EditorConfig-editor-code-style.png" alt="Visual Studio EditorConfig editor for code style settings" /></p>

<p>You can see that in addition to the typical whitespace settings, it also lists many language-specific settings as well, allowing everyone on your team to have consistent settings for the repository.
There are too many settings to fit them all in the screenshots, and new ones are periodically added with new versions of C#, .NET, and other languages.</p>

<p>The GUI is great for quickly seeing all of the available settings and their possible values.
You can still view the .editorconfig file’s plain text by using the <code class="language-plaintext highlighter-rouge">View.ViewCode</code> shortcut (<kbd>F7</kbd>).</p>

<p>One thing I don’t particularly like is that as soon as you open the file in the GUI editor, it automatically adds every setting to the text file.
This balloons my typical ~10 line .editorconfig file to around 100 lines.
I would prefer it if it only added settings that were changed from their default value (like how VS Code does with it’s settings.json file), but since it’s a file that typically isn’t viewed or changed often, it’s not too big of a deal.</p>

<p>The EditorConfig GUI is available in Visual Studio 2022 (and possibly earlier versions, but I’m not sure).
You can read more about the EditorConfig support in Visual Studio in <a href="https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options">the official documentation</a>.</p>

<p>I hope you found this useful.
Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio" /><category term="Productivity" /><category term="Editor" /><category term="IDE" /><category term="Visual Studio" /><category term="Productivity" /><category term="Editor" /><category term="IDE" /><category term="EditorConfig" /><summary type="html"><![CDATA[Somehow I missed it, but for years Visual Studio has had a built-in EditorConfig editor. I would typically edit my .editorconfig file in VS Code using just text, but Visual Studio’s editor provides a nice UI for it so you don’t have to remember all the setting names.]]></summary></entry><entry><title type="html">A PowerShell function to easily retry any code</title><link href="https://blog.danskingdom.com/A-PowerShell-function-to-easily-retry-any-code/" rel="alternate" type="text/html" title="A PowerShell function to easily retry any code" /><published>2024-08-21T00:00:00+00:00</published><updated>2024-08-21T00:00:00+00:00</updated><id>https://blog.danskingdom.com/A-PowerShell-function-to-easily-retry-any-code</id><content type="html" xml:base="https://blog.danskingdom.com/A-PowerShell-function-to-easily-retry-any-code/"><![CDATA[<p>Performing retries to make your code more resilient is a common pattern.
By leveraging a PowerShell <code class="language-plaintext highlighter-rouge">ScriptBlock</code>, we can create a function to avoid constantly rewriting the same retry logic again and again.</p>

<h2 id="tldr">TL;DR</h2>

<p>This post shows how you can build a PowerShell function to easily retry any PowerShell code that produces a terminating or non-terminating error.
If you want to skip the explanation and evolution of the code, <a href="#more-examples">jump to the bottom of this post</a> to see the final function and examples, or <a href="https://gist.github.com/deadlydog/620808036d309c8fa2606f32e5ef2f42">view them on my GitHub gist here</a>.</p>

<h2 id="traditional-retry-logic">Traditional retry logic</h2>

<p>Here’s the traditional way that you might write some code with retry logic:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$maxNumberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Do-Something</span><span class="w">
    </span><span class="nx">break</span><span class="w"> </span><span class="c"># Break out of the while-loop since the command succeeded.</span><span class="w">
  </span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$numberOfAttempts</span><span class="o">++</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$maxNumberOfAttempts</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="kr">throw</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Seconds</span><span class="w"> </span><span class="nx">3</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can see that the code above will attempt to perform the <code class="language-plaintext highlighter-rouge">Do-Something</code> command up to 5 times, waiting 3 seconds between each attempt.
If all 5 attempts fail, it will throw the exception.</p>

<h2 id="the-basic-concept-of-a-retry-function">The basic concept of a retry function</h2>

<p>Now suppose later in your code you need to call <code class="language-plaintext highlighter-rouge">Do-AnotherThing</code>, and then <code class="language-plaintext highlighter-rouge">Do-SomeOtherThing</code>, and you wanted retry logic for those as well.
Rather than repeating all of the above code, we can wrap it in a function and pass in the <code class="language-plaintext highlighter-rouge">ScriptBlock</code> to execute.</p>

<p>Here is what the function might look like:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="kr">param</span><span class="w">
  </span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
  </span><span class="p">)</span><span class="w">

  </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
  </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="w">
      </span><span class="kr">break</span><span class="w"> </span><span class="c"># Break out of the while-loop since the command succeeded.</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nv">$numberOfAttempts</span><span class="o">++</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Seconds</span><span class="w"> </span><span class="nx">3</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can see that the code is pretty much identical, except the function takes in the <code class="language-plaintext highlighter-rouge">ScriptBlock</code> to execute and the maximum number of attempts as parameters, and instead of calling <code class="language-plaintext highlighter-rouge">Do-Something</code> directly, we use <code class="language-plaintext highlighter-rouge">Invoke-Command</code> to execute the <code class="language-plaintext highlighter-rouge">ScriptBlock</code>.</p>

<p>With this function defined, we can now execute our commands, with retries, like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Use positional parameters and the default maximum number of attempts.</span><span class="w">
</span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Do-Something</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="c"># Use named parameters and specify the maximum number of attempts.</span><span class="w">
</span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Do-AnotherThing</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="nt">-MaxNumberOfAttempts</span><span class="w"> </span><span class="mi">10</span><span class="w">

</span><span class="c"># You can also capture the output of the script block.</span><span class="w">
</span><span class="nv">$resultOfDoSomeOtherThing</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Do-SomeOtherThing</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="c"># The script block can contain multiple lines of code, and can be defined as a variable.</span><span class="w">
</span><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$action</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$fileLocation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\temp\file.txt"</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$fileLocation</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$newFileLocation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\temp\newfile.txt"</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$newFileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">-replace</span><span class="w"> </span><span class="s2">"old"</span><span class="p">,</span><span class="w"> </span><span class="s2">"new"</span><span class="w">
  </span><span class="n">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$newFileLocation</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$newFileContents</span><span class="w">
  </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"Successfully replaced 'old' with 'new' in file '</span><span class="nv">$fileLocation</span><span class="s2">' and saved it to '</span><span class="nv">$newFileLocation</span><span class="s2">'."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$action</span><span class="w"> </span><span class="nt">-MaxNumberOfAttempts</span><span class="w"> </span><span class="nx">3</span><span class="w">
</span></code></pre></div></div>

<h3 id="retrying-non-terminating-errors-too">Retrying non-terminating errors too</h3>

<p>You may have noticed a potential problem with our <code class="language-plaintext highlighter-rouge">Invoke-ScriptBlockWithRetries</code> function.
It will only retry terminating errors; that is, exceptions that are <code class="language-plaintext highlighter-rouge">throw</code>n, but not non-terminating errors, such as <code class="language-plaintext highlighter-rouge">Write-Error</code> when the error action is <code class="language-plaintext highlighter-rouge">Continue</code> (the default).</p>

<p>On potential solution is to convert all non-terminating errors to terminating errors by using the <code class="language-plaintext highlighter-rouge">$ErrorActionPreference</code> variable at the start of your script:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">$Error</span><span class="n">ActionPreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Stop"</span><span class="w">
</span></code></pre></div></div>

<p>This will affect the entire script though, and is likely not what you want.
Another alternative is to use the <code class="language-plaintext highlighter-rouge">-ErrorAction</code> parameter of the specific cmdlets that might produce non-terminating errors.
In our previous example, you could do this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$newFileLocation</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$newFileContents</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">Stop</span><span class="w">
</span></code></pre></div></div>

<p>This would ensure that any errors written by the <code class="language-plaintext highlighter-rouge">Set-Content</code> cmdlet would be treated as terminating errors (e.g. thrown exceptions) and would get retried.
Having to add <code class="language-plaintext highlighter-rouge">-ErrorAction Stop</code> to every cmdlet is tedious and error prone though.</p>

<p>A better way we can address this is to add a check for non-terminating errors and throw them if they occur, by making use of the <code class="language-plaintext highlighter-rouge">-ErrorVariable</code> parameter on our <code class="language-plaintext highlighter-rouge">Invoke-Command</code>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="kr">param</span><span class="w">
  </span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="p">,</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="w">
  </span><span class="p">)</span><span class="w">

  </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
  </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="w"> </span><span class="nt">-ErrorVariable</span><span class="w"> </span><span class="nx">nonTerminatingErrors</span><span class="w">

      </span><span class="c"># Check for non-terminating errors and throw them so they get retried.</span><span class="w">
      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$nonTerminatingErrors</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w"> </span><span class="nv">$nonTerminatingErrors</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="kr">break</span><span class="w"> </span><span class="c"># Break out of the while-loop since the command succeeded.</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nv">$numberOfAttempts</span><span class="o">++</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Seconds</span><span class="w"> </span><span class="nx">3</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Now both terminating and non-terminating errors will be retried 🙂</p>

<h2 id="making-the-function-more-flexible">Making the function more flexible</h2>

<p>Now that we have a nice reusable function, let’s improve it to make it more flexible and use parameter validation.
We can add parameters so the user can configure how long to wait between retries, whether to use exponential backoff, provide a list of errors that should not be retried, and whether to retry non-terminating errors or not:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
  </span><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The script block to execute."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateNotNull</span><span class="p">()]</span><span class="w">
    </span><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The maximum number of times to attempt the script block when it returns an error."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateRange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The number of milliseconds to wait between retry attempts."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateRange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$MillisecondsToWaitBetweenAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">3000</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"If true, the number of milliseconds to wait between retry attempts will be multiplied by the number of attempts."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$ExponentialBackoff</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"List of error messages that should not be retried. If the error message contains one of these strings, the script block will not be retried."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateNotNull</span><span class="p">()]</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="bp">$Error</span><span class="n">sToNotRetry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(),</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"If true, only terminating errors (e.g. thrown exceptions) will cause the script block will be retried. By default, non-terminating errors will also trigger the script block to be retried."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DoNotRetryNonTerminatingErrors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
  </span><span class="p">)</span><span class="w">

  </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
  </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="w"> </span><span class="nt">-ErrorVariable</span><span class="w"> </span><span class="nx">nonTerminatingErrors</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$nonTerminatingErrors</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$DoNotRetryNonTerminatingErrors</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w"> </span><span class="nv">$nonTerminatingErrors</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="kr">break</span><span class="w"> </span><span class="c"># Break out of the while-loop since the command succeeded.</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nv">$numberOfAttempts</span><span class="o">++</span><span class="w">

      </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$errorMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Exception</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w">
      </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$errorDetails</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">ErrorDetails</span><span class="w">
      </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Attempt number '</span><span class="nv">$numberOfAttempts</span><span class="s2">' of '</span><span class="nv">$MaxNumberOfAttempts</span><span class="s2">' failed.</span><span class="se">`n</span><span class="s2">Error: </span><span class="nv">$errorMessage</span><span class="s2"> </span><span class="se">`n</span><span class="s2">ErrorDetails: </span><span class="nv">$errorDetails</span><span class="s2">"</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="c"># If the errorMessage contains one of the errors that should not be retried, then throw the error.</span><span class="w">
      </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$errorToNotRetry</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="bp">$Error</span><span class="n">sToNotRetry</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$errorMessage</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*</span><span class="nv">$errorToNotRetry</span><span class="s2">*"</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$errorDetails</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*</span><span class="nv">$errorToNotRetry</span><span class="s2">*"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"The string '</span><span class="nv">$errorToNotRetry</span><span class="s2">' was found in the error message, so not retrying."</span><span class="w">
          </span><span class="kr">throw</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$millisecondsToWait</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$MillisecondsToWaitBetweenAttempts</span><span class="w">
      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$ExponentialBackoff</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$millisecondsToWait</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$MillisecondsToWaitBetweenAttempts</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nv">$numberOfAttempts</span><span class="w">
      </span><span class="p">}</span><span class="w">
      </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Waiting '</span><span class="nv">$millisecondsToWait</span><span class="s2">' milliseconds before next attempt."</span><span class="w">
      </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nv">$millisecondsToWait</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Here is a contrived example of how you might use the updated function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Simulate some code that might fail in various ways.</span><span class="w">
</span><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$flakyAction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nv">$random</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Random</span><span class="w"> </span><span class="nt">-Minimum</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="nt">-Maximum</span><span class="w"> </span><span class="nx">10</span><span class="w">
  </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$random</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"Success"</span><span class="w">
  </span><span class="p">}</span><span class="w"> </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$random</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">4</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Error</span><span class="w"> </span><span class="s2">"Error"</span><span class="w">
  </span><span class="p">}</span><span class="w"> </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$random</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">6</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Error</span><span class="w"> </span><span class="s2">"Error DoNotRetry"</span><span class="w">
  </span><span class="p">}</span><span class="w"> </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$random</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"Exception"</span><span class="w">
  </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"Exception DoNotRetry"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Call our scriptblock, ensuring any exceptions or errors containing "DoNotRetry" are not retried, but all others are.</span><span class="w">
</span><span class="c"># Use the -Verbose parameter to see additional details about any failures.</span><span class="w">
</span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$flakyAction</span><span class="w"> </span><span class="nt">-MaxNumberOfAttempts</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="nt">-MillisecondsToWaitBetweenAttempts</span><span class="w"> </span><span class="nx">100</span><span class="w"> </span><span class="nt">-ExponentialBackoff</span><span class="w"> </span><span class="nt">-ErrorsToNotRetry</span><span class="w"> </span><span class="s2">"DoNotRetry"</span><span class="w"> </span><span class="nt">-Verbose</span><span class="w">
</span></code></pre></div></div>

<p>And here is the output you might see when running the above code, where it first fails with an exception, and then fails with an error, and succeeds on the 3rd attempt:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VERBOSE: Attempt number '1' of '10' failed.
Error: System.Management.Automation.RuntimeException: Exception
ErrorDetails:
VERBOSE: Waiting '100' milliseconds before next attempt.
Invoke-ScriptBlockWithRetries: C:\dev\Git\iQ\RQ.ClientCancellationProcess\Test.ps1:89:1
Line |
  89 |  Invoke-ScriptBlockWithRetries -ScriptBlock $flakyAction -MaxNumberOfA …
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Error
VERBOSE: Attempt number '2' of '10' failed.
Error: System.Management.Automation.RuntimeException: Error
ErrorDetails:
VERBOSE: Waiting '200' milliseconds before next attempt.
Success
</code></pre></div></div>

<p>If you do not want non-terminating errors to be retried (e.g. the <code class="language-plaintext highlighter-rouge">Write-Error</code> cases in the <code class="language-plaintext highlighter-rouge">$flakyAction</code> above), provide the <code class="language-plaintext highlighter-rouge">-DoNotRetryNonTerminatingErrors</code> switch parameter.</p>

<p>You may have noticed that the verbose output includes the error message and error details.
This is because some cmdlets, such as <code class="language-plaintext highlighter-rouge">Invoke-WebRequest</code>, sometimes put the error message in the ErrorDetails property.</p>

<h3 id="more-examples">More examples</h3>

<p>Here are some more practical examples:</p>

<h4 id="example-1-stop-a-service-if-it-exists">Example 1: Stop a service if it exists</h4>

<p>This will only retry if the service “SomeService” exists.
If it doesn’t, the error message “Cannot find any service with service name ‘SomeService’.” would be returned and the function won’t bother retrying.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Stop-Service</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"SomeService"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="nt">-ErrorsToNotRetry</span><span class="w"> </span><span class="s1">'Cannot find any service with service name'</span><span class="w">
</span></code></pre></div></div>

<h4 id="example-2-validate-a-web-request-was-successful">Example 2: Validate a web request was successful</h4>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$exampleThatReturnsData</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">Invoke-WebRequest</span><span class="w"> </span><span class="nt">-Uri</span><span class="w"> </span><span class="s1">'https://google.com'</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$exampleThatReturnsData</span><span class="w"> </span><span class="nt">-MaxNumberOfAttempts</span><span class="w"> </span><span class="nx">3</span><span class="w">

</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$result</span><span class="o">.</span><span class="nf">StatusCode</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="mi">200</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"Success"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Note: PowerShell 6+ have built-in Retry parameters on the <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest">Invoke-WebRequest</a> and <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod">Invoke-RestMethod</a> cmdlets that could be used instead.</p>

<h4 id="example-3-dealing-with-failures">Example 3: Dealing with failures</h4>

<p>If you want to take additional actions on failures that still occur after all of the retries, you can catch the exception and handle it as needed.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$nonExistentWebAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'https://SomeAddressThatDoesNotExist.com'</span><span class="w">

</span><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$exampleThatWillAlwaysFail</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">Invoke-WebRequest</span><span class="w"> </span><span class="nt">-Uri</span><span class="w"> </span><span class="nv">$nonExistentWebAddress</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$exampleThatWillAlwaysFail</span><span class="w"> </span><span class="nt">-MillisecondsToWaitBetweenAttempts</span><span class="w"> </span><span class="nx">100</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nv">$exceptionMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Exception</span><span class="o">.</span><span class="nf">Message</span><span class="w">
  </span><span class="n">Write-Error</span><span class="w"> </span><span class="s2">"An error occurred calling '</span><span class="nv">$nonExistentWebAddress</span><span class="s2">': </span><span class="nv">$exceptionMessage</span><span class="s2">"</span><span class="w">
  </span><span class="kr">throw</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="example-4-do-not-retry-expected-errors">Example 4: Do not retry expected errors</h3>

<p>There may be certain errors that are expected in certain situations, or where you know a retry will not help.
To save time, specify not to retry on these errors:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$exampleThatReturnsData</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">Invoke-RestMethod</span><span class="w"> </span><span class="nt">-Uri</span><span class="w"> </span><span class="s1">'https://api.google.com'</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$noRetryMessages</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
  </span><span class="s1">'400 (Bad Request)'</span><span class="w">
  </span><span class="s1">'401 (Unauthorized)'</span><span class="w">
  </span><span class="s1">'404 (Not Found)'</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$exampleThatReturnsData</span><span class="w"> </span><span class="nt">-ErrorsToNotRetry</span><span class="w"> </span><span class="nv">$noRetryMessages</span><span class="w">
</span></code></pre></div></div>

<h3 id="example-5-perform-multiple-actions">Example 5: Perform multiple actions</h3>

<p>Because we use a scriptblock, we can perform multiple actions and if any of them fail, the entire scriptblock will be retried:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">scripblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$getDataAndWriteItToAFileAndSendSlackMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Invoke-RestMethod</span><span class="w"> </span><span class="nt">-Uri</span><span class="w"> </span><span class="s1">'https://SomeApi.com/data'</span><span class="w">
  </span><span class="nv">$data</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s1">'C:\temp\data.txt'</span><span class="w">
  </span><span class="n">Send-SlackMessage</span><span class="w"> </span><span class="nt">-Message</span><span class="w"> </span><span class="s2">"Data was successfully retrieved and saved to 'C:\temp\data.txt'."</span><span class="w"> </span><span class="nt">-Channel</span><span class="w"> </span><span class="s1">'#general'</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$getDataAndWriteItToAFileAndSendSlackMessage</span><span class="w">
</span></code></pre></div></div>

<h2 id="final-version-of-the-function">Final version of the function</h2>

<p>One caveat with the above implementation is that non-terminating errors will be thrown as terminating errors if they are still failing after all of the retries, which may not be the desired behavior.
I typically prefer to have all persisting errors thrown, as it allows for a single way to handle any errors produced by the script block (i.e. with a try-catch block).</p>

<p>For those that do not want this behaviour, I offer this final implementation that is not quite as straightforward, but provides an additional <code class="language-plaintext highlighter-rouge">DoNotThrowNonTerminatingErrors</code> parameter that allows for non-terminating errors to not be thrown if they are still failing after all of the retries:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Invoke-ScriptBlockWithRetries</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">(</span><span class="bp">DefaultParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'RetryNonTerminatingErrors'</span><span class="p">)]</span><span class="w">
  </span><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The script block to execute."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateNotNull</span><span class="p">()]</span><span class="w">
    </span><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The maximum number of times to attempt the script block when it returns an error."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateRange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"The number of milliseconds to wait between retry attempts."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateRange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$MillisecondsToWaitBetweenAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">3000</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"If true, the number of milliseconds to wait between retry attempts will be multiplied by the number of attempts."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$ExponentialBackoff</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"List of error messages that should not be retried. If the error message contains one of these strings, the script block will not be retried."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidateNotNull</span><span class="p">()]</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="bp">$Error</span><span class="n">sToNotRetry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(),</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">ParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'IgnoreNonTerminatingErrors'</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"If true, only terminating errors (e.g. thrown exceptions) will cause the script block will be retried. By default, non-terminating errors will also trigger the script block to be retried."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DoNotRetryNonTerminatingErrors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">ParameterSetName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'RetryNonTerminatingErrors'</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"If true, any non-terminating errors that occur on the final retry attempt will not be thrown as a terminating error."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DoNotThrowNonTerminatingErrors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
  </span><span class="p">)</span><span class="w">

  </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
  </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$ScriptBlock</span><span class="w"> </span><span class="nt">-ErrorVariable</span><span class="w"> </span><span class="nx">nonTerminatingErrors</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$nonTerminatingErrors</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$DoNotRetryNonTerminatingErrors</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w"> </span><span class="nv">$nonTerminatingErrors</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="kr">break</span><span class="w"> </span><span class="c"># Break out of the while-loop since the command succeeded.</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="w"> </span><span class="nv">$shouldRetry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
      </span><span class="nv">$numberOfAttempts</span><span class="o">++</span><span class="w">

      </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$errorMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Exception</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w">
      </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$errorDetails</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">ErrorDetails</span><span class="w">
      </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Attempt number '</span><span class="nv">$numberOfAttempts</span><span class="s2">' of '</span><span class="nv">$MaxNumberOfAttempts</span><span class="s2">' failed.</span><span class="se">`n</span><span class="s2">Error: </span><span class="nv">$errorMessage</span><span class="s2"> </span><span class="se">`n</span><span class="s2">ErrorDetails: </span><span class="nv">$errorDetails</span><span class="s2">"</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfAttempts</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$MaxNumberOfAttempts</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$shouldRetry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$shouldRetry</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="c"># If the errorMessage contains one of the errors that should not be retried, then do not retry.</span><span class="w">
        </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$errorToNotRetry</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="bp">$Error</span><span class="n">sToNotRetry</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$errorMessage</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*</span><span class="nv">$errorToNotRetry</span><span class="s2">*"</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$errorDetails</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"*</span><span class="nv">$errorToNotRetry</span><span class="s2">*"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"The string '</span><span class="nv">$errorToNotRetry</span><span class="s2">' was found in the error message, so not retrying."</span><span class="w">
            </span><span class="nv">$shouldRetry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
            </span><span class="kr">break</span><span class="w"> </span><span class="c"># Break out of the foreach-loop since we found a match.</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$shouldRetry</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="w"> </span><span class="nv">$isNonTerminatingError</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">TargetObject</span><span class="w"> </span><span class="o">-is</span><span class="w"> </span><span class="p">[</span><span class="n">System.Collections.ArrayList</span><span class="p">]</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$isNonTerminatingError</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$DoNotThrowNonTerminatingErrors</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="kr">break</span><span class="w"> </span><span class="c"># Just break out of the while-loop since the error was already written to the error stream.</span><span class="w">
        </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="kr">throw</span><span class="w"> </span><span class="c"># Throw the error so it's obvious one occurred.</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">

      </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$millisecondsToWait</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$MillisecondsToWaitBetweenAttempts</span><span class="w">
      </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$ExponentialBackoff</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$millisecondsToWait</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$MillisecondsToWaitBetweenAttempts</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nv">$numberOfAttempts</span><span class="w">
      </span><span class="p">}</span><span class="w">
      </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Waiting '</span><span class="nv">$millisecondsToWait</span><span class="s2">' milliseconds before next attempt."</span><span class="w">
      </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nv">$millisecondsToWait</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can also <a href="https://gist.github.com/deadlydog/620808036d309c8fa2606f32e5ef2f42">find this implementation and examples on my GitHub gist here</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>By using a function like <code class="language-plaintext highlighter-rouge">Invoke-ScriptBlockWithRetries</code>, you can make your scripts more resilient to failures and avoid repeating the same retry logic over and over.</p>

<p>This is a function that I use in many of my scripts.
Feel free to use it as-is, or update it to suit your needs.
Have feedback or suggestions?
Let me know by leaving a comment below.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="PowerShell" /><summary type="html"><![CDATA[Performing retries to make your code more resilient is a common pattern. By leveraging a PowerShell ScriptBlock, we can create a function to avoid constantly rewriting the same retry logic again and again.]]></summary></entry><entry><title type="html">Common Run commands to access various Windows settings and apps</title><link href="https://blog.danskingdom.com/Common-Run-commands-to-access-various-Windows-settings-and-apps/" rel="alternate" type="text/html" title="Common Run commands to access various Windows settings and apps" /><published>2024-06-01T00:00:00+00:00</published><updated>2025-12-15T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Common-Run-commands-to-access-various-Windows-settings-and-apps</id><content type="html" xml:base="https://blog.danskingdom.com/Common-Run-commands-to-access-various-Windows-settings-and-apps/"><![CDATA[<p>There are a lot of different windows for configuring various settings in Windows.
Unfortunately, finding the right window is not always easy.
The Windows Settings menu often buries settings windows several layers deep, and they are often moved between major Windows updates, so it can be hard to find them.
The Start menu search functionality isn’t always consistent or reliable, especially on Windows Server, and even the Windows Settings search can be hit or miss.</p>

<p>For a consistent way to access many settings and apps regardless of your Windows version, you can use a command prompt or the
Run dialog box.</p>

<h2 id="opening-the-run-dialog-box">Opening the Run dialog box</h2>

<p>There are two main ways to open the Run dialog box:</p>

<ol>
  <li>Press <kbd>Win</kbd> + <kbd>R</kbd> on your keyboard, or</li>
  <li>Press <kbd>Win</kbd> to open the Start menu, type <code class="language-plaintext highlighter-rouge">Run</code>, and press <code class="language-plaintext highlighter-rouge">Enter</code>.</li>
</ol>

<p>The Run dialog box will look something like this:</p>

<p><img src="/assets/Posts/2024-06-01-Common-Run-commands-to-access-various-Windows-settings-and-apps/windows-run-dialog-box-screenshot.png" alt="Run dialog box" /></p>

<h2 id="opening-a-command-prompt">Opening a command prompt</h2>

<p>Alternatively, you can open a command prompt, such as <code class="language-plaintext highlighter-rouge">cmd</code> or <code class="language-plaintext highlighter-rouge">PowerShell</code>.
Here are two ways to do that:</p>

<ol>
  <li>Press <kbd>Win</kbd> + <kbd>X</kbd> to open the Windows X menu, then <kbd>i</kbd> to open PowerShell (use <kbd>A</kbd> instead to open a PowerShell Admin console).</li>
  <li>Press <kbd>Win</kbd> to open the Start menu, type <code class="language-plaintext highlighter-rouge">cmd</code> or <code class="language-plaintext highlighter-rouge">PowerShell</code>, and press <code class="language-plaintext highlighter-rouge">Enter</code>.</li>
</ol>

<h2 id="common-run-commands">Common Run commands</h2>

<p>With either a command prompt or the Run dialog box open, type in one of the following commands and press <code class="language-plaintext highlighter-rouge">Enter</code> to open the corresponding Windows setting or app.</p>

<p>Note: If using a command prompt, it may need to be running As Admin to open some of these settings windows.
A few commands only work from the Run dialog box and are marked as <code class="language-plaintext highlighter-rouge">(Run only)</code>.</p>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>.</td>
      <td>Open current user folder (Run only)</td>
    </tr>
    <tr>
      <td>appwiz.cpl</td>
      <td>Programs and Features</td>
    </tr>
    <tr>
      <td>C: (or any file or directory path)</td>
      <td>Open the specified file or path (Run only)</td>
    </tr>
    <tr>
      <td>certlm.msc</td>
      <td>Certificate Manager (Local Computer)</td>
    </tr>
    <tr>
      <td>certmgr.msc</td>
      <td>Certificate Manager (Current User)</td>
    </tr>
    <tr>
      <td>charmap</td>
      <td>Character Map</td>
    </tr>
    <tr>
      <td>cleanmgr</td>
      <td>Disk Cleanup</td>
    </tr>
    <tr>
      <td>cmd</td>
      <td>Command Prompt</td>
    </tr>
    <tr>
      <td>compmgmt.msc</td>
      <td>Computer Management</td>
    </tr>
    <tr>
      <td>control</td>
      <td>Control Panel</td>
    </tr>
    <tr>
      <td>control admintools</td>
      <td>Administrative Tools</td>
    </tr>
    <tr>
      <td>control desktop</td>
      <td>Personalization</td>
    </tr>
    <tr>
      <td>control folders</td>
      <td>File Explorer Options</td>
    </tr>
    <tr>
      <td>control keyboard</td>
      <td>Keyboard Properties</td>
    </tr>
    <tr>
      <td>control mouse</td>
      <td>Mouse Properties</td>
    </tr>
    <tr>
      <td>control printers</td>
      <td>Printers and Faxes</td>
    </tr>
    <tr>
      <td>control schedtasks</td>
      <td>Task Scheduler</td>
    </tr>
    <tr>
      <td>control userpasswords2</td>
      <td>User Accounts</td>
    </tr>
    <tr>
      <td>control /name Microsoft.NetworkAndSharingCenter</td>
      <td>Network and Sharing Center</td>
    </tr>
    <tr>
      <td>control /name Microsoft.PowerOptions</td>
      <td>Power Options</td>
    </tr>
    <tr>
      <td>control /name Microsoft.System</td>
      <td>System</td>
    </tr>
    <tr>
      <td>control /name Microsoft.WindowsUpdate</td>
      <td>Windows Update</td>
    </tr>
    <tr>
      <td>desk.cpl</td>
      <td>Display Properties</td>
    </tr>
    <tr>
      <td>devmgmt.msc</td>
      <td>Device Manager</td>
    </tr>
    <tr>
      <td>diskmgmt.msc</td>
      <td>Disk Management</td>
    </tr>
    <tr>
      <td>dsa.msc</td>
      <td>Active Directory Users and Computers</td>
    </tr>
    <tr>
      <td>dxdiag</td>
      <td>DirectX Diagnostic Tool</td>
    </tr>
    <tr>
      <td>eventvwr.msc</td>
      <td>Event Viewer</td>
    </tr>
    <tr>
      <td>explorer</td>
      <td>Windows Explorer</td>
    </tr>
    <tr>
      <td>firewall.cpl</td>
      <td>Windows Firewall</td>
    </tr>
    <tr>
      <td>gpedit.msc</td>
      <td>Local Group Policy Editor</td>
    </tr>
    <tr>
      <td>inetcpl.cpl</td>
      <td>Internet Properties</td>
    </tr>
    <tr>
      <td>inetmgr</td>
      <td>IIS Manager (if installed) (Run only)</td>
    </tr>
    <tr>
      <td>logoff</td>
      <td>Log out of Windows without confirmation</td>
    </tr>
    <tr>
      <td>lusrmgr.msc</td>
      <td>Local Users and Groups</td>
    </tr>
    <tr>
      <td>magnify</td>
      <td>Magnifier</td>
    </tr>
    <tr>
      <td>main.cpl</td>
      <td>Mouse Settings</td>
    </tr>
    <tr>
      <td>mdsched</td>
      <td>Windows Memory Diagnostic</td>
    </tr>
    <tr>
      <td>mmc</td>
      <td>Microsoft Management Console</td>
    </tr>
    <tr>
      <td>mmsys.cpl</td>
      <td>Sound Properties</td>
    </tr>
    <tr>
      <td>mrt</td>
      <td>Malware Removal Tool</td>
    </tr>
    <tr>
      <td>msconfig</td>
      <td>System Configuration</td>
    </tr>
    <tr>
      <td>msinfo32</td>
      <td>System Information</td>
    </tr>
    <tr>
      <td>mstsc</td>
      <td>Remote Desktop Connection</td>
    </tr>
    <tr>
      <td>ncpa.cpl</td>
      <td>Network Connections</td>
    </tr>
    <tr>
      <td>netplwiz</td>
      <td>User Accounts</td>
    </tr>
    <tr>
      <td>osk</td>
      <td>On-Screen Keyboard</td>
    </tr>
    <tr>
      <td>perfmon.msc</td>
      <td>Performance Monitor</td>
    </tr>
    <tr>
      <td>powercfg.cpl</td>
      <td>Power Options</td>
    </tr>
    <tr>
      <td>powershell</td>
      <td>Windows PowerShell Console</td>
    </tr>
    <tr>
      <td>psr</td>
      <td>Steps Recorder</td>
    </tr>
    <tr>
      <td>pwsh</td>
      <td>PowerShell Core Console (if installed)</td>
    </tr>
    <tr>
      <td>regedit</td>
      <td>Registry Editor</td>
    </tr>
    <tr>
      <td>resmon</td>
      <td>Resource Monitor</td>
    </tr>
    <tr>
      <td>secpol.msc</td>
      <td>Local Security Policy</td>
    </tr>
    <tr>
      <td>services.msc</td>
      <td>Services</td>
    </tr>
    <tr>
      <td>shell:appsFolder</td>
      <td>Applications folder (see installed apps) (Run only)</td>
    </tr>
    <tr>
      <td>shell:desktop</td>
      <td>Desktop folder (Run only)</td>
    </tr>
    <tr>
      <td>shell:downloads</td>
      <td>Downloads folder (Run only)</td>
    </tr>
    <tr>
      <td>shell:sendto</td>
      <td>SendTo folder of the current user (Run only)</td>
    </tr>
    <tr>
      <td>shell:common sendto</td>
      <td>SendTo folder for all users (Run only)</td>
    </tr>
    <tr>
      <td>shell:startup</td>
      <td>Startup folder of the current user (Run only)</td>
    </tr>
    <tr>
      <td>shell:common startup</td>
      <td>Startup folder for all users (Run only)</td>
    </tr>
    <tr>
      <td>shutdown /r /t 0</td>
      <td>Restart Windows without confirmation</td>
    </tr>
    <tr>
      <td>shutdown /s /t 0</td>
      <td>Shut down Windows without confirmation</td>
    </tr>
    <tr>
      <td>snippingtool</td>
      <td>Screenshot Snipping Tool</td>
    </tr>
    <tr>
      <td>sysdm.cpl</td>
      <td>System Properties</td>
    </tr>
    <tr>
      <td>taskmgr</td>
      <td>Task Manager</td>
    </tr>
    <tr>
      <td>temp</td>
      <td>Temp folder of Windows (Run only)</td>
    </tr>
    <tr>
      <td>%temp%</td>
      <td>Temp folder of the current user (Run only)</td>
    </tr>
    <tr>
      <td>winver</td>
      <td>About Windows</td>
    </tr>
    <tr>
      <td>wscui.cpl</td>
      <td>Security and Maintenance</td>
    </tr>
  </tbody>
</table>

<p>This blog article was inspired by <a href="https://www.linkedin.com/feed/update/urn:li:activity:7202019020282245120/">this LinkedIn post</a> which shows how to define many of these in a PowerShell function that can be used to search for the command you want.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I hope you find this list of commands useful.
If you have any other commands that you find valuable, please share them in the comments below and I might add them to this list.
I intend to keep this list updated as I discover more useful commands.</p>

<p>If you liked this post, you might also like my <a href="https://blog.danskingdom.com/Handy-Windows-Command-Prompt-commands/">Handy Windows Command Prompt commands post</a>.</p>

<p>Happy running!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="Windows" /><category term="Productivity" /><category term="Windows" /><summary type="html"><![CDATA[There are a lot of different windows for configuring various settings in Windows. Unfortunately, finding the right window is not always easy. The Windows Settings menu often buries settings windows several layers deep, and they are often moved between major Windows updates, so it can be hard to find them. The Start menu search functionality isn’t always consistent or reliable, especially on Windows Server, and even the Windows Settings search can be hit or miss.]]></summary></entry><entry><title type="html">Why I switched to OTBS for PowerShell brace styling</title><link href="https://blog.danskingdom.com/Why-I-switched-to-OTBS-for-PowerShell-brace-styling/" rel="alternate" type="text/html" title="Why I switched to OTBS for PowerShell brace styling" /><published>2024-05-27T00:00:00+00:00</published><updated>2024-05-29T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Why-I-switched-to-OTBS-for-PowerShell-brace-styling</id><content type="html" xml:base="https://blog.danskingdom.com/Why-I-switched-to-OTBS-for-PowerShell-brace-styling/"><![CDATA[<p>Code formatting can be a very personal choice.
Some languages are very strict and enforce specific indentation and brace styles, while many others allow for different indentation styles to be used, including PowerShell.
Many languages end up with a defacto style that most people use, often because it is the default style in a popular editor or IDE.
You should take care when choosing your style though, as sometimes the style can make scripts harder to maintain, or even lead to bugs in your code.</p>

<h2 id="allman-and-one-true-brace-style-otbs">Allman and One True Brace Style (OTBS)</h2>

<p>Before diving into PowerShell over a decade ago, I was primarily a C# developer.
The typical convention for C# has been to use <a href="https://en.wikipedia.org/wiki/Indentation_style#Allman_style">the Allman indentation style</a> for code.
I love the Allman style.
To me, it’s clean and makes code easier to read.
Here’s an example of the Allman style:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">ReplaceDogWithCat</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$textToPrint</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$text</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$textToPrint</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$text</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'dog'</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Output</span><span class="w"> </span><span class="s1">'cat'</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Output</span><span class="w"> </span><span class="nv">$text</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>If you’ve looked at open source PowerShell code, you’ve probably noticed that many people use <a href="https://en.wikipedia.org/wiki/Indentation_style#One_True_Brace">the One True Brace Style (OTBS)</a> for brace styling.
Here’s the same example formatted using the OTBS style:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">ReplaceDogWithCat</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$textToPrint</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$text</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$textToPrint</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$text</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'dog'</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Output</span><span class="w"> </span><span class="s1">'cat'</span><span class="w">
        </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Output</span><span class="w"> </span><span class="nv">$text</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Most fans of OTBS think that the Allman style wastes too much vertical space by putting the opening brace on a new line.
To me though, that whitespace makes the code easier to read; it visually segregates the blocks of code allowing my mind to more easily focus on a single block.</p>

<p>When I look at the OTBS example above, my mind initially just sees one big block of text, rather than 4 distinct parts (function, foreach, if, else).
Maybe it’s because I’ve been using Allman for so long, but the compactness of OTBS makes it feel cluttered to me.</p>

<h2 id="why-use-otbs-for-powershell">Why use OTBS for PowerShell?</h2>

<p>So if I love Allman style, why did I switch to OTBS for PowerShell?</p>

<p>There are two main reasons:</p>

<ol>
  <li>OTBS is more popular in the PowerShell community.</li>
  <li>PowerShell has some restrictions that force the opening brace to be on the same line in some scenarios.</li>
</ol>

<h3 id="popularity">Popularity</h3>

<p>Regardless of what style you prefer, you should use it consistently.
If you’re working on a team, it’s best to agree on a common style and stick to it, otherwise you’ll end up with a mix of styles in your files, which can be a bit jarring and lead to confusion.</p>

<p>That said, it’s often best to try and use the most popular style.
It means that when others need to work on your scripts, or when you bring new members onto your team, they’re more likely to be familiar with the style you’re using.
They won’t have to spend time trying to understand the style you’re using, or updating their editor settings to match your style.</p>

<p>While I don’t have any hard proof that OTBS is the most popular, it’s what I’ve personally seen most often in open source projects.
Also, it’s by far the most up-voted style on <a href="https://github.com/PoshCode/PowerShellPracticeAndStyle/discussions/177">Joel Bennett’s GitHub discussion on the subject</a>, which is linked to from the VS Code settings.</p>

<h3 id="powershell-restrictions">PowerShell restrictions</h3>

<p>I’ve been using Allman style for PowerShell for over a decade.
In the beginning I stumbled a bit, as there are a few scenarios that require the opening brace to be on the same line.
The main examples are any cmdlet that uses a script block, such as <code class="language-plaintext highlighter-rouge">ForEach-Object</code>, <code class="language-plaintext highlighter-rouge">Where-Object</code>, <code class="language-plaintext highlighter-rouge">Invoke-Command</code>, etc.
Many statements work fine with the opening brace on a new line though.</p>

<p>Here are some code examples to illustrate the issue:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># WORKS: The opening brace on a new line works fine for `foreach` loops.</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$number</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$number</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># WORKS: It works fine for `if` and `switch` statements too.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$number</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$number</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># FAILS: This will halt execution to prompt for a script block.</span><span class="w">
</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="bp">$_</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># WORKS: The above needs to be written like this to work correctly:</span><span class="w">
</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="bp">$_</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># FAILS: This will also halt and prompt the user for a script block.</span><span class="w">
</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="bp">$_</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># WORKS: The above needs to be written like this to work correctly:</span><span class="w">
</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="bp">$_</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># FAILS: This will throw an error that no value was provided for `-ScriptBlock` parameter.</span><span class="w">
</span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Get-Process</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># WORKS: The above needs to be written like this to work correctly:</span><span class="w">
</span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Get-Process</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Those last 3 examples require the opening brace <code class="language-plaintext highlighter-rouge">{</code> to be on the same line as the cmdlet.
After running into these issues for a few months, I was able to remember when an opening brace is required to be on the same line, and it has become second nature to me.
It often boils down to whether it is a PowerShell keyword (e.g. if, foreach, switch), a function vs. a cmdlet, and if you are using implicit parameters or not.</p>

<p>That’s the problem though.
It took me months to get it engrained which scenarios require the opening brace to be on the same line.
Even though it’s second nature to me now, it’s not going to be for everyone, especially those new to PowerShell.
Why put others through months (years?) of pain to learn when it’s ok to put the opening brace on a new line and when it’s not?
Why potentially introduce bugs into your scripts, simply for the sake of a brace style?</p>

<p>It’s important to note that VS Code / PowerShell / PSScriptAnalyzer does not give any warnings or indications that the code will fail.
It is perfectly valid syntax.
This is what makes these so dangerous.
The code will not fail until runtime.
This means if you have the problematic code in a non-happy path, the script might run completely fine for months before finally hitting that code path and failing.</p>

<p>This is the primary reason I’ve switched to OTBS for PowerShell.
I’m not the only person who works on my scripts, and I’m producing more open-source PowerShell these days.
I want to create a pit of success for others working with my code; it should be hard to do the wrong thing.
Using Allman in PowerShell means inconsistency in where the opening brace goes, creating a minefield for others to navigate.
It simply isn’t worth the confusion and potential bugs it introduces.</p>

<p>If like me you find Allman more readable, you can experiment with inserting a blank line between blocks of code to help visually segregate them (e.g. where you used to have the opening <code class="language-plaintext highlighter-rouge">{</code> brace in it’s own line) and see if that helps.</p>

<h2 id="setting-the-default-brace-style-in-vs-code">Setting the default brace style in VS Code</h2>

<p>Hopefully this post has illustrated why I’ve switched to OTBS for PowerShell, and has you considering doing the same.
You can set your default PowerShell brace/indentation style in Visual Studio Code by using the <code class="language-plaintext highlighter-rouge">powershell.codeFormatting.preset</code> setting.
Just search for <code class="language-plaintext highlighter-rouge">PowerShell format</code> in the VS Code Settings and you should find it.
Here you can see I have it set to OTBS:</p>

<p><img src="/assets/Posts/2024-05-27-Why-I-switched-to-OTBS-for-PowerShell-brace-styling/vs-code-powershell-code-formatting-setting.png" alt="Visual Studio Code PowerShell code formatting preset setting set to OTBS" /></p>

<p>If you’re not ready to make it your default everywhere and just want to enable it for specific projects, or if you’re not the only one working on your project, switch to the <code class="language-plaintext highlighter-rouge">Workspace</code> tab and update the setting there.
This will create a <code class="language-plaintext highlighter-rouge">.vscode/settings.json</code> file in your project with the setting in it:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"powershell.codeFormatting.preset"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OTBS"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This allows you to commit the setting to source control, ensuring that everyone who works on the project uses the same brace style.</p>

<h2 id="updating-your-existing-scripts">Updating your existing scripts</h2>

<p>After updating VS Code’s PowerShell code formatting setting, you can use the <code class="language-plaintext highlighter-rouge">Format Document</code> command in VS Code to update the brace style of the current file.</p>

<p>If you want to update all of the scripts in your project at once, check out <a href="https://marketplace.visualstudio.com/items?itemName=jbockle.jbockle-format-files">the Format Files extension</a>.</p>

<p>It’s worth noting that VS Code will not automatically change the formatting of the problematic code statements mentioned in the examples above, whether you have Allman or OTBS configured, since it is valid syntax.
So do not expect running <code class="language-plaintext highlighter-rouge">Format Document</code> to fix those issues.</p>

<p>The main motivation to reformat your code to use OTBS is so others looking at the code will be more likely to always put the opening brace on the same line, since that is how the rest of the script is formatted.</p>

<h2 id="conclusion">Conclusion</h2>

<p>While code formatting is often just a personal choice, in PowerShell the brace style is not purely cosmetic.
It can easily lead to bugs if not used correctly.
To create a pit of success by not having to remember when the opening brace must be on the same line, switch to using OTBS for PowerShell.</p>

<p>Have you found this information helpful?
Do you disagree with my reasoning?
I’d love to hear your thoughts in the comments below.</p>

<p>Happy scripting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Visual Studio Code" /><category term="PowerShell" /><category term="Visual Studio Code" /><summary type="html"><![CDATA[Code formatting can be a very personal choice. Some languages are very strict and enforce specific indentation and brace styles, while many others allow for different indentation styles to be used, including PowerShell. Many languages end up with a defacto style that most people use, often because it is the default style in a popular editor or IDE. You should take care when choosing your style though, as sometimes the style can make scripts harder to maintain, or even lead to bugs in your code.]]></summary></entry><entry><title type="html">A simple PowerShell script template I use when creating new scripts</title><link href="https://blog.danskingdom.com/A-simple-PowerShell-script-template-I-use-when-creating-new-scripts/" rel="alternate" type="text/html" title="A simple PowerShell script template I use when creating new scripts" /><published>2024-05-24T00:00:00+00:00</published><updated>2024-05-29T00:00:00+00:00</updated><id>https://blog.danskingdom.com/A-simple-PowerShell-script-template-I-use-when-creating-new-scripts</id><content type="html" xml:base="https://blog.danskingdom.com/A-simple-PowerShell-script-template-I-use-when-creating-new-scripts/"><![CDATA[<p>I spin up new PowerShell scripts all the time, whether just for quickly experimenting and testing things out, or for projects that I know will be around for a while.
There’s a few basic things that I like all my scripts to have, so I’ve created a simple template that I use when creating new standalone scripts.</p>

<p>The script template provides:</p>

<ul>
  <li>A basic script structure with <code class="language-plaintext highlighter-rouge">[CmdletBinding()]</code> enabled.</li>
  <li><code class="language-plaintext highlighter-rouge">$InformationPreference</code> enabled to show <code class="language-plaintext highlighter-rouge">Write-Information</code> messages.</li>
  <li>Displays the time the script started and ended, and how long it took to run.</li>
  <li>Uses <code class="language-plaintext highlighter-rouge">Start-Transcript</code> to log the script output from the last run to a file.
The log file will have the same name as the script, with <code class="language-plaintext highlighter-rouge">.LastRun.log</code> appended to it.
    <ul>
      <li>Logging to a transcript file will incur a small performance hit, so if you need your script to be super performant, and depending on how much output your script generates, you may want to remove this.</li>
      <li>If your script outputs sensitive information such as passwords or secrets, you may want to remove this, since they could end up in the unencrypted log file.</li>
    </ul>
  </li>
</ul>

<p>Using a template allows me to get up and running quickly, and ensures all of my scripts have a consistent structure.</p>

<p>Here’s the script template code:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">&lt;#
  </span><span class="cs">.SYNOPSIS</span><span class="cm">
  PUT SHORT SCRIPT DESCRIPTION HERE AND ADD ANY ADDITIONAL KEYWORD SECTIONS AS NEEDED (</span><span class="cs">.PARAMETER</span><span class="cm">, </span><span class="cs">.EXAMPLE</span><span class="cm">, ETC.).
#&gt;</span><span class="w">
</span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
</span><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
  </span><span class="c"># PUT PARAMETER DEFINITIONS HERE AND DELETE THIS COMMENT.</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="kr">process</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="c"># PUT SCRIPT CODE HERE AND DELETE THIS COMMENT.</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">begin</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="c"># DEFINE FUNCTIONS HERE AND DELETE THIS COMMENT.</span><span class="w">

  </span><span class="nv">$InformationPreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Continue'</span><span class="w">
  </span><span class="c"># $VerbosePreference = 'Continue' # Uncomment this line if you want to see verbose messages.</span><span class="w">

  </span><span class="c"># Log all script output to a file for easy reference later if needed.</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$lastRunLogFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="bp">$PSCommandPath</span><span class="s2">.LastRun.log"</span><span class="w">
  </span><span class="n">Start-Transcript</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$lastRunLogFilePath</span><span class="w">

  </span><span class="c"># Display the time that this script started running.</span><span class="w">
  </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$startTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Date</span><span class="w">
  </span><span class="nx">Write-Information</span><span class="w"> </span><span class="s2">"Starting script at '</span><span class="si">$(</span><span class="nv">$startTime</span><span class="o">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s1">'u'</span><span class="p">)</span><span class="si">)</span><span class="s2">'."</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">end</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="c"># Display the time that this script finished running, and how long it took to run.</span><span class="w">
  </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$finishTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Date</span><span class="w">
  </span><span class="p">[</span><span class="n">TimeSpan</span><span class="p">]</span><span class="w"> </span><span class="nv">$elapsedTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$finishTime</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nv">$startTime</span><span class="w">
  </span><span class="n">Write-Information</span><span class="w"> </span><span class="s2">"Finished script at '</span><span class="si">$(</span><span class="nv">$finishTime</span><span class="o">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s1">'u'</span><span class="p">)</span><span class="si">)</span><span class="s2">'. Took '</span><span class="nv">$elapsedTime</span><span class="s2">' to run."</span><span class="w">

  </span><span class="n">Stop-Transcript</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>I also have the template stored as <a href="https://gist.github.com/deadlydog/d04b5d43170a90d8bc0143373d90010f">a GitHub gist here</a>, which may be more up-to-date than the code in this post.</p>

<p>I often use this template to create standalone scripts that I run directly, rather than calling them from other scripts (I would typically create a module instead for those types of reusable functions).
This is the reason that I set the <code class="language-plaintext highlighter-rouge">$InformationPreference</code> and <code class="language-plaintext highlighter-rouge">$VerbosePreference</code> in the <code class="language-plaintext highlighter-rouge">begin</code> block and start a transcript.
If you’re calling the script from another script, you likely want to remove those lines and rely on the calling script to pass in the preference parameters via the CmdletBinding and start the transcript itself.</p>

<p>I typically define all of my functions in the <code class="language-plaintext highlighter-rouge">begin</code> block, and then call them from the <code class="language-plaintext highlighter-rouge">process</code> block as needed.
I also put the <code class="language-plaintext highlighter-rouge">process</code> block before the <code class="language-plaintext highlighter-rouge">begin</code> and <code class="language-plaintext highlighter-rouge">end</code> blocks.
This helps keep the primary script code (in the <code class="language-plaintext highlighter-rouge">process</code> block) front-and-center at the top of the script, and makes it easier to see what the script is doing at a glance.
If I need to see the details of a function I can use <kbd>F12</kbd> to jump to the function definition.
Often times I just want a high-level overview of what the script is doing though, and reading the <code class="language-plaintext highlighter-rouge">process</code> block provides that.
I like to think of the <code class="language-plaintext highlighter-rouge">process</code> block like the table of contents or list of chapters in a book; it gives you a quick high-level summary of everything in the book and allows you to jump straight to the section you’re interested in, without having to read the entire book.</p>

<p>In a traditional script without a <code class="language-plaintext highlighter-rouge">begin</code> block, you would need to define all of your functions before you call them, meaning you’d have to scroll down and hunt for where the script code actually starts executing commands.
Following the convention of defining functions in the <code class="language-plaintext highlighter-rouge">begin</code> block, you can just look at the <code class="language-plaintext highlighter-rouge">process</code> block to see where the script actually starts executing non-boilerplate code.</p>

<p>Here is a contrived example of how a script using this template might look:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">&lt;#
  </span><span class="cs">.SYNOPSIS</span><span class="cm">
  Writes the specified text to a file.

  </span><span class="cs">.DESCRIPTION</span><span class="cm">
  This script writes a given text to a specified file, creating the directory if it doesn't exist.

  </span><span class="cs">.PARAMETER</span><span class="cm"> TextToWriteToFile
  The text to write to the file.

  </span><span class="cs">.PARAMETER</span><span class="cm"> FilePath
  The file path to write the text to, overwriting the file if it already exists.

  </span><span class="cs">.EXAMPLE</span><span class="cm">
  .\Script.ps1 -TextToWriteToFile "Sample Text" -FilePath "C:\Temp\Test.txt"

  </span><span class="cs">.NOTES</span><span class="cm">
  Ensure that you have the necessary permissions to write to the specified file path.
#&gt;</span><span class="w">
</span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
</span><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
  </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'The text to write to the file.'</span><span class="p">)]</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$TextToWriteToFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Hello, World!'</span><span class="p">,</span><span class="w">

  </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'The file path to write the text to.'</span><span class="p">)]</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$FilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="bp">$PSScriptRoot</span><span class="s2">\Test.txt"</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="kr">process</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="n">Ensure-DirectoryExists</span><span class="w"> </span><span class="nt">-directoryPath</span><span class="w"> </span><span class="p">(</span><span class="n">Split-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FilePath</span><span class="w"> </span><span class="nt">-Parent</span><span class="p">)</span><span class="w">

  </span><span class="n">Write-Information</span><span class="w"> </span><span class="s2">"Writing the text '</span><span class="nv">$TextToWriteToFile</span><span class="s2">' to the file '</span><span class="nv">$FilePath</span><span class="s2">'."</span><span class="w">
  </span><span class="n">Write-TextToFile</span><span class="w"> </span><span class="nt">-text</span><span class="w"> </span><span class="nv">$TextToWriteToFile</span><span class="w"> </span><span class="nt">-filePath</span><span class="w"> </span><span class="nv">$FilePath</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">begin</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="kr">function</span><span class="w"> </span><span class="nf">Ensure-DirectoryExists</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$directoryPath</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$directoryPath</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Container</span><span class="p">))</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="n">Write-Information</span><span class="w"> </span><span class="s2">"Creating directory '</span><span class="nv">$directoryPath</span><span class="s2">'."</span><span class="w">
      </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$directoryPath</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">

  </span><span class="kr">function</span><span class="w"> </span><span class="nf">Write-TextToFile</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$text</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$filePath</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$filePath</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"File '</span><span class="nv">$filePath</span><span class="s2">' already exists. Overwriting it."</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$filePath</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$text</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
  </span><span class="p">}</span><span class="w">

  </span><span class="nv">$InformationPreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Continue'</span><span class="w">
  </span><span class="c"># $VerbosePreference = 'Continue' # Uncomment this line if you want to see verbose messages.</span><span class="w">

  </span><span class="c"># Log all script output to a file for easy reference later if needed.</span><span class="w">
  </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$lastRunLogFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="bp">$PSCommandPath</span><span class="s2">.LastRun.log"</span><span class="w">
  </span><span class="n">Start-Transcript</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$lastRunLogFilePath</span><span class="w">

  </span><span class="c"># Display the time that this script started running.</span><span class="w">
  </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$startTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Date</span><span class="w">
  </span><span class="nx">Write-Information</span><span class="w"> </span><span class="s2">"Starting script at '</span><span class="si">$(</span><span class="nv">$startTime</span><span class="o">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s1">'u'</span><span class="p">)</span><span class="si">)</span><span class="s2">'."</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">end</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="c"># Display the time that this script finished running, and how long it took to run.</span><span class="w">
  </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$finishTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Date</span><span class="w">
  </span><span class="p">[</span><span class="n">TimeSpan</span><span class="p">]</span><span class="w"> </span><span class="nv">$elapsedTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$finishTime</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nv">$startTime</span><span class="w">
  </span><span class="n">Write-Information</span><span class="w"> </span><span class="s2">"Finished script at '</span><span class="si">$(</span><span class="nv">$finishTime</span><span class="o">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s1">'u'</span><span class="p">)</span><span class="si">)</span><span class="s2">'. Took '</span><span class="nv">$elapsedTime</span><span class="s2">' to run."</span><span class="w">

  </span><span class="n">Stop-Transcript</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Were you able to understand what the script does just by reading the <code class="language-plaintext highlighter-rouge">param</code> and <code class="language-plaintext highlighter-rouge">process</code> blocks at the top?
Hopefully you didn’t need to read the entire script to understand that it simply writes the provided text to the provided file path.
This is the benefit of putting the <code class="language-plaintext highlighter-rouge">process</code> block at the top of the script, and functions in the <code class="language-plaintext highlighter-rouge">begin</code> block.
Also, often times the code in the <code class="language-plaintext highlighter-rouge">begin</code> and <code class="language-plaintext highlighter-rouge">end</code> blocks are complimentary to each other, so it makes sense to keep them together.</p>

<p>You may argue that the Synopsis in the comment-based help provided that information as well, and in this simple trivial example you are correct.
The comment-based help should only describe the goal of the script and how to use it, not the individual steps of how it accomplishes that goal.
In longer scripts with many steps, the <code class="language-plaintext highlighter-rouge">process</code> block can provide the implementation overview or summary.
I’ll note though that the <code class="language-plaintext highlighter-rouge">process</code> block will only give you that nice high-level overview of the script steps if you write your code for it.
If you just dump all of the code directly in the <code class="language-plaintext highlighter-rouge">process</code> block you won’t have a nice overview; if you break the steps out into well named functions though, it can be easy to read through and understand the high-level script steps.
Not everyone wants to write their code this way though, and that’s fine; it’s just what I’ve found works well for me and my team, making our scripts easier to reason about and maintain.</p>

<p>This template is just my own personal preference, and I’m sure there are other things you may want to add to it.
Feel free to use it as-is, or modify it to suit your own needs.
I personally like to keep my template minimal and only include the things that I find myself adding to every script.
You may want to create an “all the bells and whistles” template to start from, or different templates for different types of scripts (e.g. a different template for non-standalone scripts).</p>

<p>The main point is having a boilerplate template to start from can save you time and help ensure that all of your scripts have a consistent feel.</p>

<p>Do you use a template for your scripts?
Are you going to start after reading this?
Is there anything you would add or change in the template I provided?
Let me know in the comments below!</p>

<p>Hopefully you found this useful.
Happy scripting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Productivity" /><category term="PowerShell" /><category term="Productivity" /><summary type="html"><![CDATA[I spin up new PowerShell scripts all the time, whether just for quickly experimenting and testing things out, or for projects that I know will be around for a while. There’s a few basic things that I like all my scripts to have, so I’ve created a simple template that I use when creating new standalone scripts.]]></summary></entry><entry><title type="html">PowerShell + DevOps Global Summit 2024 reflections</title><link href="https://blog.danskingdom.com/PowerShell-DevOps-Global-Summit-2024-reflections/" rel="alternate" type="text/html" title="PowerShell + DevOps Global Summit 2024 reflections" /><published>2024-04-12T00:00:00+00:00</published><updated>2024-05-13T00:00:00+00:00</updated><id>https://blog.danskingdom.com/PowerShell-DevOps-Global-Summit-2024-reflections</id><content type="html" xml:base="https://blog.danskingdom.com/PowerShell-DevOps-Global-Summit-2024-reflections/"><![CDATA[<p>I’m sitting in the airport waiting for my return flight after what has been an awesome week at <a href="https://www.powershellsummit.org">the PowerShell + DevOps Global Summit 2024</a>, North American edition in Bellevue, Washington.
There’s a whirlwind of things going through my mind, so I thought I would try and capture some of them here.</p>

<h2 id="context">Context</h2>

<p>To give some context to my ramblings, I’ve been writing software for about 25 years, using PowerShell for over a decade, and have been fortunate to attend a number of conferences over the past 20 years.
The first few were smaller conferences, but the past 15 years have typically been very Very VERY large conferences, such as Microsoft Build, TechEd, and Ignite.
We’re talking 10,000 attendees, where you might have lunch with a group of people the first day and then never see them again the entire conference.
To accommodate that many attendees, there were hundreds (thousands?) of sessions to pick from, and any time between sessions was often spent trying to make your way to another building to get to your next session before it started.</p>

<p>This was my first time attending the PowerShell Summit, my first time delivering a recorded presentation outside of my work, and my first time in a long time attending a smaller conference.
Also remember that this is the point of view of a single attendee; others may have had a different experience, so take this post with a grain of salt.</p>

<h2 id="the-summit">The Summit</h2>

<p>The PowerShell Summit was the opposite of these big conferences in so many ways.
There were only ~400 attendees, so you were seeing the same faces each day.
The conference was held in the Meydenbauer Center, with all of the sessions up on the 4th floor in one of 5 rooms that were all next door to each other.
This meant the time between sessions was spent talking with the other attendees or one of the few vendors, rather than trying to find the next session room.
Seeing the same faces every day meant having multiple conversations with the same individuals, and made it easier to strike up conversations and move past the usual smalltalk that most people go through when awkwardly meeting someone for the first time.</p>

<p>I loved getting to hear how their companies are operating, what struggles they or the company were going through, and how they are solving them.
I was often able to give my perspective and tell them how I or my company had solved a similar problem or gone through a similar experience, and steer them toward a strategy or tools that could help them out.
I always find these types of conversations very interesting, and get a sense of reward when I’m able to point them in a direction that will help them out.
Of course there was other non-tech conversations as well, like talk about video games, home towns, the eclipse that happened on Monday, and various other things.</p>

<p>I’m not going to lie and say I made friends with everyone there.
There were still many people that I never interacted with at all.
I did however feel very comfortable talking with pretty much everyone there.
Everyone was very welcoming.</p>

<p>I typically prefer to interact with people one on one or in small groups, as that often allows for deeper conversations.
I don’t do great with surface small talk, and found myself avoiding some of the larger groups in the evenings due to my own anxiety.
If I attend next year though, perhaps that will change as I’ll be more familiar with more of the returning attendees.
I mention this not as a bad thing, but more-so because if you’re the type of person who enjoys the fun party group in the evenings, you’ll find some of them there.
There were of course many people who opted out of the evening activities, or who also stayed in smaller groups during them, so there was a good mix of personalities.</p>

<p>Speaking of the evening activities, they were great, and there was more of them than I was expecting.
Even though the conference started on Monday, there was a mixer on Sunday to get everyone acquainted.
There was a fun game night on Monday with arcade games, axe throwing, and casino games.
On Thursday there was a party with mini-golf, virtual golf, pool, ping pong, and an open bar.
This left us with 2 evenings to do whatever we wanted.
I went out for supper with a great group of people; other people went sightseeing.
The Slack channel was very active and made it easy to find others to hang out with if you wanted to.</p>

<p>Also, because most attendees were staying in the same hotel, most nights you could wander down to the lobby and find a group of people to hang out and chat with if you liked.
I definitely enjoyed this aspect of the conference and would encourage others to stay in the official hotel as well, as it allows you to mingle some more in the evenings if you are feeling up to it.</p>

<h3 id="vendors">Vendors</h3>

<p>Conferences like Microsoft Ignite have 100s of vendors there whose sole purpose is to try and sell you on their products.
They entice you with swag, like t-shirts, power banks, stress balls, cables, chargers, socks, raffles (often you must be present to win), and so much more, but often to get it you have to <strike>suffer</strike> sit through a 5 minute (or longer) sales pitch
I’ll admit that I’m a sucker for swag, and would typically bring a large bag of it back to the office to give away.</p>

<p>Summit was nothing like that.
I believe there were only 5 or 6 vendors there, and they were not pushy at all.
They still had some swag to give out which was great (I love myself some tech T-shirts 🙂), but it was very relaxed and casual conversations; not sitting through a 5 minute sales pitch, which was great.</p>

<h3 id="content-and-presenters">Content and presenters</h3>

<p>I’ve been using PowerShell for over a decade and use social media to try and stay up to date with official announcements and unofficial community rumblings.
Due to this, I didn’t see too many things from the presentations that I wasn’t already aware of.
Almost everything shown was something that I already had experience with, or that I had seen talked about in a social media or blog post at some point.
Granted, there were often 5 sessions happening simultaneously and I could only attend one, so I’m sure I missed out on many great things and am looking forward to watching the recordings in the coming weeks.
Also, I talked to many people who were newer to PowerShell (1 - 5 years), or who didn’t regularly keep up with the latest tech news, and they were getting a lot of real world value to take home from almost every session.
The fact that I didn’t learn as much from many of the sessions is more of a reflection of my experience level and the particular sessions that I chose to attend, not a reflection of the quality of the content or presenters.
There were a couple presentations that I did learn quite a lot, such as how to write your own feedback providers, and one on using machine learning in your Prometheus metrics for early anomaly detection and alerting.</p>

<p>Reflecting back, I need to remember to adjust my expectations for this type of conference.
At conferences like MS Build or Ignite, there are TONS of new product announcements.
In almost every session you learn about something new, because they just built it and are announcing it for the first time.
In reality, the entire conference is one big sales pitch to try and sell you on their latest products and features.
To get you to buy in and be locked in.
This is why they can pour so much money into those huge conferences; they are trying to get you to buy their products.</p>

<p>The PowerShell Summit is nothing like that.
They aren’t there to announce a bunch of new features that the PowerShell team created (although there were a few), or to get you to buy a product.
It is a conference by the community, for the community, to share knowledge and experiences with each other.
And community isn’t something that only happens once a year at the Summit; it happens all year long on social media, <a href="https://forums.powershell.org">the PowerShell forums</a>, <a href="https://discord.gg/powershell">the PowerShell Discord server</a>, and blogs.
Summit offers the opportunity to meet and interact with this great community in person and to dive deep into topics, which is something that is hard to do asynchronously online.</p>

<p>Much of the session content was a good refresher for me, and I did still learn quite a few new things.
Particularly, there were a number of community modules that I had never heard of, or had heard of but never really explored, and getting to see them in action was great.
There’s a couple that I’ll be adding to my toolkit.
Also, getting to talk with the presenters afterward was valuable as it allowed me to ask questions and dive deeper into the content they delivered.</p>

<p>I must say too that the quality of the presenters was top-notch.
I was surprised, considering the majority of them are just other community members like myself.
Usually at other conferences there would be at least a couple sessions where I would think to myself, “This person is not cut out for presenting”.
I never had that thought once at the Summit.
Even if I was already familiar with the content, I still enjoyed the delivery, and found most presenters did a great job answering questions on the spot.
I was very impressed.</p>

<h3 id="presenting">Presenting</h3>

<p>Speaking of presenting, I also presented a 45 minute session.
I was quite nervous beforehand, but had many attendees afterward tell me that they really enjoyed the presentation and thought it went great.
I ran a little over my time and had to skip my last two slides, and had a hiccup in my demos where GitHub Actions had a network issue publishing the module to the PowerShell Gallery and had to be reran, but overall I am happy with how everything turned out.
I had a number of people ask questions afterward, and had some great conversations later with others about the content.</p>

<p><img src="/assets/Posts/2024-04-12-PowerShell-DevOps-Global-Summit-2024-reflections/dan-presenting.jpg" alt="Dan presenting" /></p>

<p>Since this was both my first time attending Summit and my first time presenting, I wanted to do something special.
I created stickers to promote my <a href="https://github.com/deadlydog/PowerShell.tiPS">tiPS module</a>, and created a few t-shirts to give away to those who asked questions in my session.</p>

<p><img src="/assets/Posts/2024-04-12-PowerShell-DevOps-Global-Summit-2024-reflections/tiPS-sticker.jpg" alt="tiPS stickers" />
<img src="/assets/Posts/2024-04-12-PowerShell-DevOps-Global-Summit-2024-reflections/tiPS-shirt.jpg" alt="tiPS t-shirt" /></p>

<p>As a presenter, I’ll say that the process of getting set up and ready to present was easy and very smooth.
I’m looking forward to seeing the feedback that attendees left for me, watching the recording once it is posted in a few weeks, and am hoping to present again next year.</p>

<blockquote>
  <p>Update May 13, 2024: You can now find the recording of my presentation on the <a href="https://www.youtube.com/watch?v=oM_2sOE9Y6g">PowerShell.org YouTube channel here</a>, and <a href="https://github.com/deadlydog/Presentation.StopManuallyPublishingYourPowerShellModules">my slide deck here</a>.</p>
</blockquote>

<h2 id="summary">Summary</h2>

<p><strong>So was the conference worth it for me?</strong>
I would definitely say yes, but for different reasons than the big conferences I had become accustom to.
At those conferences I would leave with 20 pages in my notebook of things to try when I get home, because they just released hundreds of new products.
At Summit I still had a few pages of things to further investigate, but also had many more people added to my LinkedIn and Twitter to keep in contact with.
This conference definitely helped grow my personal network more than any other conference in the past, which is especially important with the waves of layoffs that the tech community has been going through.
I liked that I was able to chat with experts and get a couple questions around best practices answered that I had been wondering about.
I also had very deep conversations with a handful of individuals, like Chris Masters and <a href="https://twitter.com/DevOpsJeremy">DevOps Jeremy</a>.
If I attend next year, I’ll probably spend a bit more time in the hallway track, interacting with other community members and having more in-depth conversations with them.</p>

<p><strong>Would I recommend this conference to someone else?</strong>
Most definitely.
While I personally didn’t get as much value from the sessions as I hoped due to my experience level, anyone who is novice to intermediate with PowerShell would definitely get a lot of value from the sessions, and it is a great way to level up their PowerShell skills.
And of course, the networking opportunities are great, and it allows you to follow up and dive deep with other community experts to gain more clarity on topics you are interested in.</p>

<p>One of the coolest things about Summit was getting to meet and interact with people that I had been following on Twitter for years.
Getting to put a face to the name (as some people use random pics for their online presence), shake their hand, let them know how much I appreciate their contributions, and have real conversations with them was awesome.
People like <a href="https://twitter.com/joshooaj">Josh Hendricks</a>, <a href="https://twitter.com/JeffHicks">Jeff Hicks</a>, <a href="https://twitter.com/JustinWGrote">Justin Grote</a>, <a href="https://twitter.com/JamesBru">James Brundage</a>, and many others.</p>

<p>Finally, I must say that <a href="https://twitter.com/AndrewPlaTech">Andrew Pla</a> is a machine.
Andrew <a href="/I-was-the-guest-for-the-100th-episode-of-The-PowerShell-Podcast/">had me as a guest on The PowerShell Podcast</a> a few months ago, and I was excited to meet him in person.
After working the PDQ sponsor booth for much of the day during the sessions, he was then doing interviews for several hours every evening for <a href="https://www.pdq.com/resources/the-powershell-podcast">The PowerShell Podcast</a>.
I was super grateful for some downtime in the evenings between the sessions ending and the evening activities, but Andrew never seemed to get that and always had to be on.
Whatever PDQ is paying him, he deserves a raise!</p>

<p>What was your experience at the PowerShell Summit like?
Are you planning to attend next year?
Let me know in the comments below.</p>

<h2 id="bonus">Bonus</h2>

<p>For those of you who have made it this far, I have a special announcement.
I was originally planning to demo this during the lightning demos, but all of the speaker’s lightning demos got bumped off the docket since we received so many attendee submissions, which is great!</p>

<p>The week before Summit I spent a few hours with a proof of concept and released a new module called <a href="https://github.com/deadlydog/PowerShell.dumPS">dumPS</a>.
It’s designed to help make exploring and debugging PowerShell from the terminal a bit quicker and easier.
It’s still in the early stages and I need to add some better defaults and more functionality to it, but am excited to see where it goes.
Check it out and let me know what you think.</p>

<blockquote>
  <p>Update May 7, 2024: I also wrote <a href="https://www.iqmetrix.com/blog/iqmetrix-expertise-in-the-wild-contributing-to-the-powershell-community">this other blog post</a> for my company if you want to read more about my experience.</p>
</blockquote>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Conference" /><category term="Social" /><category term="PowerShell" /><category term="Conference" /><category term="Social" /><summary type="html"><![CDATA[I’m sitting in the airport waiting for my return flight after what has been an awesome week at the PowerShell + DevOps Global Summit 2024, North American edition in Bellevue, Washington. There’s a whirlwind of things going through my mind, so I thought I would try and capture some of them here.]]></summary></entry><entry><title type="html">Easily format all files in your VS Code workspace</title><link href="https://blog.danskingdom.com/2024-03-12-Easily-format-all-files-in-your-VS-Code-workspace/" rel="alternate" type="text/html" title="Easily format all files in your VS Code workspace" /><published>2024-03-12T00:00:00+00:00</published><updated>2024-03-12T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Easily-format-all-files-in-your-VS-Code-workspace</id><content type="html" xml:base="https://blog.danskingdom.com/2024-03-12-Easily-format-all-files-in-your-VS-Code-workspace/"><![CDATA[<p>I recently discovered the awesome <a href="https://marketplace.visualstudio.com/items?itemName=jbockle.jbockle-format-files">Format Files VS Code extension</a> that allows you to format all files in your workspace.
Typically you would need to open each file in Visual Studio Code and run the <code class="language-plaintext highlighter-rouge">Format Document</code> command manually, but with this extension you can format all files with a single command.
This allows you to easily ensure all files are formatted consistently.
This is especially useful if you have a large codebase and change your code formatting settings.
e.g. changing your <code class="language-plaintext highlighter-rouge">.editorconfig</code> or <code class="language-plaintext highlighter-rouge">.prettierrc</code> settings, or modifying your VS Code code formatting settings.</p>

<p>The extension still needs to manually open each file in the editor and apply the formatting, so it can take a little while to run, but it sure beats doing it manually.
I hope you find this extension as helpful as I have.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio Code" /><category term="Editor" /><category term="Productivity" /><category term="Extension" /><category term="Visual Studio Code" /><category term="Editor" /><category term="Productivity" /><category term="Extension" /><category term="VS Code" /><summary type="html"><![CDATA[I recently discovered the awesome Format Files VS Code extension that allows you to format all files in your workspace. Typically you would need to open each file in Visual Studio Code and run the Format Document command manually, but with this extension you can format all files with a single command. This allows you to easily ensure all files are formatted consistently. This is especially useful if you have a large codebase and change your code formatting settings. e.g. changing your .editorconfig or .prettierrc settings, or modifying your VS Code code formatting settings.]]></summary></entry><entry><title type="html">Find your Azure resources nearing end-of-life with the Service Retirement Workbook</title><link href="https://blog.danskingdom.com/Find-your-Azure-resources-nearing-end-of-life-with-the-Service-Retirement-Workbook/" rel="alternate" type="text/html" title="Find your Azure resources nearing end-of-life with the Service Retirement Workbook" /><published>2024-02-08T00:00:00+00:00</published><updated>2024-02-08T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Find-your-Azure-resources-nearing-end-of-life-with-the-Service-Retirement-Workbook</id><content type="html" xml:base="https://blog.danskingdom.com/Find-your-Azure-resources-nearing-end-of-life-with-the-Service-Retirement-Workbook/"><![CDATA[<p>The Azure portal has a Service Retirement Workbook that shows all of the services you are using which are being retired within the next few years.
This could be things like having to update your apps because Azure no longer supports TLS 1.0/1.1, old versions of Node.js or .NET, or the retiring of a service like Classic Cloud Services.
This is extremely helpful when your organization has many teams using a lot of different services, and they need time to plan the update or migration work into their roadmaps.
Shout out to my colleague Amer Gill for showing me this!</p>

<p>To access the view in the Azure portal, open up the <code class="language-plaintext highlighter-rouge">Advisor</code>, navigate to <code class="language-plaintext highlighter-rouge">Workbooks</code> (or use <a href="https://portal.azure.com/#view/Microsoft_Azure_Expert/AdvisorMenuBlade/~/workbooks">this link</a>), and open the <code class="language-plaintext highlighter-rouge">Service Retirement (Preview)</code> workbook.</p>

<p><img src="/assets/Posts/2024-02-08-Find-your-Azure-resources-nearing-end-of-life-with-the-Service-Retirement-Workbook/navigate-to-services-retirement-workbook-in-azure-advisor.png" alt="Navigating to the Service Retirement Workbook" /></p>

<p>At the top you will see a list of services you are using that are being retired soon, the date they are being retired, how many of your resources will be affected, and a link to documentation about the retirement and actions to take.</p>

<p><img src="/assets/Posts/2024-02-08-Find-your-Azure-resources-nearing-end-of-life-with-the-Service-Retirement-Workbook/azure-services-retirement-workbook-list-of-services-retiring.png" alt="Service Retirement Workbook list of services" /></p>

<p>Below that is a list of resources that will be affected by the service retirements you have selected in the list above.
This tells you the resources’ subscription, resource group, name, type, location, tags, and a link to actions to take.</p>

<p><img src="/assets/Posts/2024-02-08-Find-your-Azure-resources-nearing-end-of-life-with-the-Service-Retirement-Workbook/list-of-resources-affected-by-service-retirement.png" alt="Service Retirement Workbook list of resources" /></p>

<p>You can also export the results to Excel, making the information easy to share with others.</p>

<p>Having a single up-to-date view of all the services being retired, which resources will be affected, and links to documentation on actions that need to be taken is extremely helpful and convenient.
Anybody in the organization is able to view the workbook, and as teams migrate their resources to newer services they are removed from the list, giving a realtime view of which resources still need attention.</p>

<p><strong>Recommended Action:</strong> Set up a recurring reminder to check this workbook every month or quarter, so you can stay on top of any upcoming retirements that may affect you or your organization.
Share this information with other teams so they can do the same.</p>

<p>If you are curious or just like to stay up-to-date, you can also toggle the workbook view to show all upcoming service retirements, not just retirements for the services that you are currently using.
There is also <a href="https://azure.microsoft.com/en-us/updates/?updateType=retirements">the Azure Updates page</a> that lists upcoming and past retirements, can be searched, and has an RSS feed that you can subscribe to.</p>

<p>Did you find this information as helpful as I did?
Have any other related tips?
Let me know in the comments below.</p>

<p>Cheers!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Azure" /><category term="Azure" /><summary type="html"><![CDATA[The Azure portal has a Service Retirement Workbook that shows all of the services you are using which are being retired within the next few years. This could be things like having to update your apps because Azure no longer supports TLS 1.0/1.1, old versions of Node.js or .NET, or the retiring of a service like Classic Cloud Services. This is extremely helpful when your organization has many teams using a lot of different services, and they need time to plan the update or migration work into their roadmaps. Shout out to my colleague Amer Gill for showing me this!]]></summary></entry><entry><title type="html">Several ways to explore your Azure resources</title><link href="https://blog.danskingdom.com/Several-ways-to-explore-your-Azure-resources/" rel="alternate" type="text/html" title="Several ways to explore your Azure resources" /><published>2024-01-19T00:00:00+00:00</published><updated>2024-01-24T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Several-ways-to-explore-your-Azure-resources</id><content type="html" xml:base="https://blog.danskingdom.com/Several-ways-to-explore-your-Azure-resources/"><![CDATA[<p>Here I present a few ways to explore your Azure resources, for when the portal’s search and filter functionality is not enough.</p>

<h2 id="context">Context</h2>

<p>I needed to find all of the Azure <code class="language-plaintext highlighter-rouge">Cloud service (classic)</code> resources that we had in our organization, since that service is <a href="https://azure.microsoft.com/en-ca/updates/cloud-services-retirement-announcement/">being retired on August 31, 2024</a>.
I initially overlooked the Azure portal’s <code class="language-plaintext highlighter-rouge">All Resources</code> blade, which would have allowed me to filter by resource type.
Along the way I discovered some other ways to browse and filter my Azure resources, and thought I’d share them here.</p>

<p>Spoiler: I show how to find the Classic services in the Azure Portal and Azure Resource Graph Explorer sections below.</p>

<blockquote>
  <p>Update: For an even better way to track down services that are being retired, check out my post on the <a href="/Find-your-Azure-resources-nearing-end-of-life-with-the-Service-Retirement-Workbook/">Azure Service Retirement Workbook</a>.</p>
</blockquote>

<h2 id="ways-to-explore-your-azure-resources">Ways to explore your Azure resources</h2>

<p>It is important to note that all of the approaches below require you to have access to the Azure subscription you want to explore.
You can only browse and search across subscriptions you have permissions for.
If you are wanting to search your entire organization, ensure you have access to all subscriptions.</p>

<h3 id="azure-portal">Azure portal</h3>

<p>The easiest and most user-friendly way to explore your Azure resources is through the Azure portal: <a href="https://portal.azure.com">https://portal.azure.com</a>.</p>

<p>You can use the global search bar at the top center of the web page to search for resources by name, resource type (service), or resource group.
If you want to just browse your resources, navigate to the <code class="language-plaintext highlighter-rouge">Subscriptions</code> blade and then drill down into the resource groups and their resources to inspect them.</p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/azure-portal-search-by-resource-name-screenshot.png" alt="Search Azure Portal by resource name" /></p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/azure-portal-search-by-service-screenshot.png" alt="Search Azure Portal by service" /></p>

<p>NOTE: To ensure you are able to view and search all of the subscriptions you have access to, click the gear icon on the top-right of the webpage and ensure the <code class="language-plaintext highlighter-rouge">Default subscription filter</code> is set to <code class="language-plaintext highlighter-rouge">All subscriptions</code>, otherwise you may not see the resources you expect.</p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/azure-portal-default-subscription-filter-screenshot.png" alt="Azure Portal default subscription filter" /></p>

<p>There is also an <code class="language-plaintext highlighter-rouge">All Resources</code> blade that will allow you to browse, search, sort, and filter all resources in the subscriptions you have access to.
It shows you the resource Name, Type, Location, Resource Group, and Subscription, and also allows you to filter your resources by Tag.
This is a powerful blade that can be useful for finding resources of a specific type, in a specific location, or with a specific tag.</p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/azure-portal-all-resources-screenshot.png" alt="Azure Portal All Resources blade" /></p>

<p>To solve my initial problem, I could use the <code class="language-plaintext highlighter-rouge">All Resources</code> blade to filter by Type <code class="language-plaintext highlighter-rouge">Cloud service (classic)</code>.</p>

<p>The portal does not always expose all of the properties of a resource, or let you search or filter by them.
If you are looking for something more specific, you may need to use another approach.</p>

<h3 id="azure-resource-explorer">Azure Resource Explorer</h3>

<p>Another way to browse resources is with the Azure Resource Explorer: <a href="https://resources.azure.com">https://resources.azure.com</a>.</p>

<p>This can show you the API endpoints for your resources, and let you explore them in a tree view.
It may also expose some properties that the portal does not.
I have found this view is not that helpful on its own, but can be when used in conjunction with the other approaches below.</p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/azure-resource-explorer-screenshot.png" alt="Azure Resource Explorer screenshot" /></p>

<p>If you do not see the resources you expect, you may need to change the directory using the dropdown box at the top.
If your organization has a lot of resources, the page can be very resource intensive and bring your browser to its knees.</p>

<h3 id="azure-powershell-module">Azure PowerShell module</h3>

<p>You can leverage the <code class="language-plaintext highlighter-rouge">Az</code> Azure PowerShell module to explore your resources from PowerShell.
The <code class="language-plaintext highlighter-rouge">Az</code> module is actually comprised of many different modules and can be quite large.
To simply install all of the modules from the PowerShell Gallery, use the command:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Az</span><span class="w"> </span><span class="nt">-Repository</span><span class="w"> </span><span class="nx">PSGallery</span><span class="w">
</span></code></pre></div></div>

<p>However, you may prefer to only install the specific modules you need.
You can view all of the various <a href="https://www.powershellgallery.com/packages?q=Az">Az modules on the PowerShell Gallery</a>.</p>

<p>To start, you may want to install just the <code class="language-plaintext highlighter-rouge">Az.Resources</code> module, which will allow you to explore your resources:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nx">Az.Resources</span><span class="w"> </span><span class="nt">-Repository</span><span class="w"> </span><span class="nx">PSGallery</span><span class="w">
</span></code></pre></div></div>

<p>Once installed, use <code class="language-plaintext highlighter-rouge">Connect-AzAccount</code> to connect to your Azure account.</p>

<p>Now that the module is installed and connected, you can use <code class="language-plaintext highlighter-rouge">Get-AzResource</code> to explore the resources in the subscription you are connected to.
To list the subscriptions you have access to, use <code class="language-plaintext highlighter-rouge">Get-AzSubscription</code>, and to change subscriptions, use <code class="language-plaintext highlighter-rouge">Set-AzContext -Subscription &lt;subscription name&gt;</code>.</p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/az-powershell-module-view-resource-screenshot.png" alt="View Azure resources with Az PowerShell module" /></p>

<p>The PowerShell module exposes more properties than the portal does, and you can use regular PowerShell commands to filter and search for resources you want.
You can also use the Az module to create, modify, and delete resources as well.
For more information, check out the <a href="https://learn.microsoft.com/en-us/powershell/azure/install-azure-powershell">Az module documentation</a>.</p>

<h3 id="azure-cli">Azure CLI</h3>

<p>If you prefer to not use PowerShell, you can use the Azure CLI instead.
You will need to <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli">download and install the package</a> that is specific to your operating system.</p>

<p>Once you have the CLI installed, use <code class="language-plaintext highlighter-rouge">az login</code> to connect to your Azure account.</p>

<p>Now that the CLI is installed and connected, you can use <code class="language-plaintext highlighter-rouge">az resource list</code> to explore the resources in the subscription you are connected to.
To view all of the subscriptions you have access to, use <code class="language-plaintext highlighter-rouge">az account list</code>, and to change subscriptions, use <code class="language-plaintext highlighter-rouge">az account set --subscription &lt;subscription name&gt;</code>.</p>

<p>The CLI has subcommands for many resource types, such as <code class="language-plaintext highlighter-rouge">az vm list</code> or <code class="language-plaintext highlighter-rouge">az webapp list</code> to list just VMs or web apps.
Use <code class="language-plaintext highlighter-rouge">az --help</code> to see all of the available commands.</p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/az-cli-view-key-vaults-screenshot.png" alt="View Azure resources with Azure CLI" /></p>

<p>The CLI may expose more properties than the portal, and allows you to also create, modify, and delete resources.
For more information, check out the <a href="https://learn.microsoft.com/en-us/cli/azure/">Azure CLI documentation</a>.</p>

<h3 id="azure-rest-api">Azure Rest API</h3>

<p>The Azure Rest API is one of the most powerful ways to explore your resources, but it is also the most difficult.</p>

<p>All of the methods mentioned above use the Rest API under the hood.
Using the Rest API does not allow for easy browsing of your resources like the approaches above, as you often need to know the API endpoint for the resource you are looking for.
This is where the Azure Resource Explorer can help, as it can show you the API endpoints for your resources.
Otherwise you often have to read through the API docs.</p>

<p>When testing the Rest API, it can be helpful to use a tool like Postman or Thunder Client for VS Code.
There are client libraries that can make is easier to use the Rest API in your own code, and they have support for languages such as .NET, Java, Node.js, and Python.</p>

<p>For more information, see the <a href="https://learn.microsoft.com/en-us/rest/api/azure/">Rest API documentation</a>.</p>

<h3 id="azure-resource-graph-explorer">Azure Resource Graph Explorer</h3>

<p>Azure Resource Graph Explorer is a service that allows you to query your Azure resources using the Kusto query language.
It is extremely powerful, and you can use it from the Portal, Azure PowerShell, Azure CLI, or the Rest API.</p>

<p>The easiest way to use it is from the Azure portal.
From the portal, use the global search bar to search for <code class="language-plaintext highlighter-rouge">Resource Graph Explorer</code>, or go to <a href="https://portal.azure.com/#view/HubsExtension/ArgQueryBlade">https://portal.azure.com/#view/HubsExtension/ArgQueryBlade</a>.</p>

<p>You will be presented with a blank query editor, a <code class="language-plaintext highlighter-rouge">Get started</code> view that has several example queries to choose from, and a tree view on the left with a search bar and the various resource types and their properties.
The example queries can show you how to use the Kusto query language, and the tree view makes it easy to add filters to your query for the resource types and properties that you are interested in.</p>

<p>For example, here is a Kusto query to retrieve all of the action groups (alerts) that send email notifications to me@domain.com; something that you cannot simply search for in the Action Groups or Alerts blade of the portal:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">resources</span><span class="w">
</span><span class="o">|</span><span class="w"> </span><span class="n">where</span><span class="w"> </span><span class="nx">type</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"microsoft.insights/actiongroups"</span><span class="w">
</span><span class="o">|</span><span class="w"> </span><span class="n">where</span><span class="w"> </span><span class="nx">properties</span><span class="p">[</span><span class="s1">'emailReceivers'</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">'emailAddress'</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"me@domain.com"</span><span class="w">
</span></code></pre></div></div>

<p>Here is a query that could be used solve my initial problem to retrieve all of the <code class="language-plaintext highlighter-rouge">Cloud service (classic)</code> resources:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">resources</span><span class="w">
</span><span class="o">|</span><span class="w"> </span><span class="n">where</span><span class="w"> </span><span class="nx">type</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s2">"microsoft.classiccompute/domainnames"</span><span class="w">
</span><span class="o">|</span><span class="w"> </span><span class="n">order</span><span class="w"> </span><span class="nx">by</span><span class="w"> </span><span class="nx">name</span><span class="w"> </span><span class="nx">desc</span><span class="w">
</span></code></pre></div></div>

<p>Here’s a screenshot of the query editor with the above query:</p>

<p><img src="/assets/Posts/2024-01-19-Several-ways-to-explore-your-Azure-resources/azure-resource-graph-explorer-screenshot.png" alt="Azure Resource Graph Explorer" /></p>

<p>If needed, I could refine the query to further filter down the results based on other resource properties.</p>

<p>The portal allows you to download the query results as a CSV file, so they are easy to share with others.
Also, unlike the PowerShell module and CLI, the Resource Graph Explorer is not scoped to a specific subscription at a time, so you can easily query across all of your subscriptions.</p>

<p>For more information, check out the <a href="https://learn.microsoft.com/en-us/azure/governance/resource-graph/">Azure Resource Graph documentation</a>.</p>

<h2 id="when-to-use-each-tool">When to use each tool</h2>

<p>So we’ve seen multiple ways you can explore your Azure resources.
When should you use each one?</p>

<blockquote>
  <p>I want to browse my resources and see what’s there.</p>
</blockquote>

<p>Use the Azure portal to navigate around your subscriptions and their resources.</p>

<blockquote>
  <p>I want to find a specific resource, a specific type of resource, or resources with a specific property.</p>
</blockquote>

<p>Start with the Portal’s global search bar.
From there, try the blade for the resource type you are looking for (e.g. the Subscriptions blade).
If those do not provide the searching and filtering you need, use the Azure Resource Graph Explorer.</p>

<blockquote>
  <p>I want to find a specific resource or property as part of an automated script.</p>
</blockquote>

<p>Use the Azure PowerShell module or Azure CLI.</p>

<blockquote>
  <p>I want to find resources as part of my application.</p>
</blockquote>

<p>Use the Azure Rest APIs with one of the client libraries.
The Azure Resource Explorer may help you find the API endpoints you need, and specific properties to filter on.</p>

<p>Of course, these are just recommendations, and you can use whichever approach you prefer for your scenario.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We’ve seen several ways to get started exploring your Azure resources.</p>

<p>For my initial problem of finding all “Cloud service (classic)” resources, I could have also used the Azure PowerShell module or Azure CLI.
The Azure Portal and Azure Resource Graph Explorer can be the easiest to get started with, as they allow you to browse through services and properties easily, which is nice when you do not know the specific properties, terms, and syntax to use.
Also, they do not require any additional setup (no software to install or connecting to Azure).
It all comes down using the tool you are most comfortable with though.</p>

<p>Hopefully you’ve learned something new and this helps you find the resources you are looking for!</p>

<p>Happy exploring!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Azure" /><category term="PowerShell" /><category term="Azure" /><category term="PowerShell" /><summary type="html"><![CDATA[Here I present a few ways to explore your Azure resources, for when the portal’s search and filter functionality is not enough.]]></summary></entry><entry><title type="html">I was the guest for the 100th episode of The PowerShell Podcast!</title><link href="https://blog.danskingdom.com/I-was-the-guest-for-the-100th-episode-of-The-PowerShell-Podcast/" rel="alternate" type="text/html" title="I was the guest for the 100th episode of The PowerShell Podcast!" /><published>2024-01-11T00:00:00+00:00</published><updated>2024-01-11T00:00:00+00:00</updated><id>https://blog.danskingdom.com/I-was-the-guest-for-the-100th-episode-of-The-PowerShell-Podcast</id><content type="html" xml:base="https://blog.danskingdom.com/I-was-the-guest-for-the-100th-episode-of-The-PowerShell-Podcast/"><![CDATA[<p>I was recently invited to be a guest on The PowerShell Podcast (<a href="https://powershellpodcast.podbean.com/">Podbean</a>) (<a href="https://www.youtube.com/playlist?list=PL1mL90yFExsjUS8DRkzfLUcHds7vlxqgM">YouTube playlist</a>) (<a href="https://podcasts.apple.com/us/podcast/the-powershell-podcast/id1616048196">Apple Podcasts</a>).
I had a great time talking with <a href="https://twitter.com/AndrewPlaTech">Andrew Pla</a>, and it was a bit surreal as I’ve listened to every episode.
This was my first time on a podcast, and I was very nervous.
You can tell because I was talking very fast, so hopefully I was still understandable to everyone 😅.</p>

<p>While I didn’t realize it until a few days after recording, I was on the show’s 100th episode!
<a href="https://youtu.be/sBpm_R1MQ38?si=sEduyCx8ONY3NfNC">Check out the episode on YouTube here</a>.
That is a huge accomplishment for the podcast, and I’m honoured to have been a part of it.
Congratulations to Andrew and the rest of the team for reaching this milestone!</p>

<p>I was a little bummed that I did not get to also meet <a href="https://twitter.com/DevOpsJordan">Jordan Hammond</a>, as he decided to take a break from the show at the end of 2023.
I missed out on meeting him by just a couple episodes.
Even though he stopped just shy of 100 episodes, it is still a great feat and he should be proud.
I hope he returns to the show in the future, even if only as a guest and to check off the “100 episodes milestone” box 😁.</p>

<h2 id="podcast-additions">Podcast additions</h2>

<p>When writing blog posts and recording videos, I have time to gather my thoughts and later update any information that I left out.
While chatting live, I don’t have that luxury.
After we were done recording I was soon thinking of all the things that I could have said better, or things that I left out and should have mentioned.</p>

<p>If I was able to edit the podcast, these are some additional things I would have mentioned:</p>

<h3 id="my-powershell-tips-module">My <a href="https://github.com/deadlydog/PowerShell.tiPS">PowerShell tiPS module</a></h3>

<ul>
  <li>The tip is delivered to your PowerShell terminal.</li>
  <li>Provides not only a PowerShell tip, but also:
    <ul>
      <li>Example code of what the tip describes.</li>
      <li>Links to external sources for more information.</li>
      <li>Categorizes the tip (e.g. Performance, Security, Syntax, Terminal, Module, Community, etc.).</li>
    </ul>
  </li>
</ul>

<h3 id="is-getting-a-certification-worth-it">Is getting a certification worth it?</h3>

<ul>
  <li>The exams can be a bit expensive, but often your employer will pay for them if you ask.
    <ul>
      <li>If you attend a conference, such as MS Build or Ignite, you can often get a free exam voucher.</li>
    </ul>
  </li>
  <li>Don’t get too discouraged if you have trouble passing the exam.
Some people are great at traditional school and taking exams, others are not.
If you are someone that struggles with traditional exams, don’t let that discourage you.
The real value is in the knowledge you gain while studying for the exam.
Also, the MS exams in particular I’ve found often have at least one or two “trick” questions, where they will word the question in such a way that the answer is not obvious, even for a subject that you know very well.</li>
</ul>

<h3 id="working-from-home">Working from home</h3>

<ul>
  <li>When working in the office you can build relationships with people you otherwise wouldn’t have.
I’m a software developer, but have built relationships with people in other departments, such as IT, Support, HR, and Finance.
Not only has this been helpful for both expected and unexpected situations (e.g. “I wonder how we bill our customers; I’ll ask Heather in Finance”), but they are also great people and I’m glad to know them.</li>
  <li>Having a Zoom/Teams call open all day long for your team where you all have the cameras off and are muted is a great way to reproduce the informal conversations that happen in the office.
You can unmute to ask a team member a question, and others that are on the call can chime in if they choose, or just listen in.
If you start a screenshare, the others can choose to watch as well if they want, and may learn something new, or tell you a better way to perform the task.
This is much more organic than scheduling a meeting, and leads to more collaboration in an low-friction way.</li>
</ul>

<h3 id="gitops">GitOps</h3>

<ul>
  <li>GitOps typically describes how the deployed service and infrastructure should look in a declarative manner, rather than having a procedural script that describes how to get to that state.
Similar to how PowerShell DSC/Puppet/Terraform/ARM templates work vs. a regular PowerShell script.
Also, while a deployment pipeline is typically used to deploy a single service, GitOps is often used to deploy an entire environment, such as a Kubernetes cluster with hundreds or thousands of services.</li>
</ul>

<h3 id="devops-sre-and-platform-engineering">DevOps, SRE, and Platform Engineering</h3>

<ul>
  <li>DevOps
    <ul>
      <li>Not only about monitoring how your application performs in production, but also how it is being used.
This is done by adding telemetry, and can tell you things like which features are being the most used, and which ones are not used at all and can potentially be removed to reduce application complexity.</li>
    </ul>
  </li>
  <li>SRE
    <ul>
      <li>Another large focus is infrastructure costs.
You want to ensure the applications have redundancy, disaster recovery, and can perform well, but without being wasteful by overprovisioning resources and paying for a bunch of compute and storage that is not utilized.</li>
      <li>Often focus on infrastructure security as well.
This could be things like ensuring applications are only able to accept https traffic (not http), and that only up to date TLS versions are allowed.</li>
    </ul>
  </li>
  <li>Platform Engineering
    <ul>
      <li>Ideally a dev team is able to just specify the git repository that contains their application code and what type of application it is (web service, background service, cron job), and the platform will take care of building and deploying the app in a safe and secure manner, including an automated rollback process in case of failure.
It may also perform additional tasks, such as static code analysis, security penetration testing, vulnerability scanning, automated load testing, and more.</li>
    </ul>
  </li>
</ul>

<h3 id="my-favourite-thing-i-use-powershell-for">My favourite thing I use PowerShell for</h3>

<ul>
  <li>Anything that requires connecting to multiple remote machines to retrieve information or run commands, such as:
    <ul>
      <li>Finding IIS website certs that are expiring soon.</li>
      <li>Testing web services locally to find which servers in a pool are not responding correctly.</li>
      <li>Pulling down log files from servers that are not yet using distributed logging.</li>
      <li>Installing or removing software or files on multiple machines.</li>
      <li>Recycling IIS app pools on multiple machines.</li>
      <li>Restarting servers en masse.</li>
    </ul>
  </li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>I try to hold the content I create to a high quality standard, and am often my own worst critic.
Writing these amendments shows that I still have some growth to do in learning how to relax and let go of things that are not perfect with the content I create.
But, I just couldn’t help myself 😅.</p>

<p>Overall I think the podcast went well and I had a great time.
Andrew was a great host, very easy to work with, and made me feel very comfortable, both before/after recording and while talking live.
He’s a true pro!
I’m looking forward to doing another show in the future.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Social" /><category term="PowerShell" /><category term="Social" /><category term="Podcast" /><summary type="html"><![CDATA[I was recently invited to be a guest on The PowerShell Podcast (Podbean) (YouTube playlist) (Apple Podcasts). I had a great time talking with Andrew Pla, and it was a bit surreal as I’ve listened to every episode. This was my first time on a podcast, and I was very nervous. You can tell because I was talking very fast, so hopefully I was still understandable to everyone 😅.]]></summary></entry><entry><title type="html">Multiple ways to setup your CI/CD pipelines in GitHub Actions</title><link href="https://blog.danskingdom.com/Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/" rel="alternate" type="text/html" title="Multiple ways to setup your CI/CD pipelines in GitHub Actions" /><published>2024-01-05T00:00:00+00:00</published><updated>2024-01-10T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions</id><content type="html" xml:base="https://blog.danskingdom.com/Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/"><![CDATA[<p>In this post I’ll show different approaches to setting up your build and deployment workflows with GitHub Actions, as well as some pros and cons of each.</p>

<h2 id="tldr">TL;DR</h2>

<p>While I show several approaches here, the one I recommend using is the “Include approach with reusable workflows” (Approach 5), so you can skip straight to that section if you like and take in the sample code.</p>

<p><a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions">This sample GitHub repository</a> contains all of the examples shown in this post, and you can see how the workflow runs look in <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions">the GitHub Actions web UI here</a>.
Feel free to fork the repo and play around with it yourself.</p>

<h2 id="background">Background</h2>

<p>Over the summer I created <a href="https://github.com/deadlydog/PowerShell.tiPS">the tiPS PowerShell module</a> in GitHub and decided to use GitHub Actions for the CI/CD process.
For the past few years I have been using Azure DevOps for my CI/CD pipelines, but I wanted to try out GitHub Actions to see how it compared, especially since my code was also hosted in GitHub.
The approaches I show here are ones I tried out in the tiPS project as it evolved, until settling on an approach I was happy with.</p>

<h2 id="terminology">Terminology</h2>

<p>Azure DevOps and much of the industry use the term “pipeline” to refer to the automated steps to build and deploy software, but GitHub Actions uses the term “workflow” instead.
For the purposes of this post, the terms “pipeline” and “workflow” are interchangeable.</p>

<p>Similarly, Azure DevOps uses the term “template”, while GitHub Actions uses the term “reusable workflow”, so I may use them interchangeably as well.</p>

<h2 id="the-approaches">The approaches</h2>

<p>Some CI/CD criteria we want to meet in our examples are:</p>

<ul>
  <li>Deploy to the <code class="language-plaintext highlighter-rouge">staging</code> environment automatically when a <code class="language-plaintext highlighter-rouge">main</code> branch build completes successfully.</li>
  <li>Manual intervention should be required to deploy to the <code class="language-plaintext highlighter-rouge">production</code> environment.
e.g. A manual approval.</li>
  <li>PR builds and non-main branch builds should:
    <ul>
      <li>Not trigger automatic deployments.</li>
      <li>Manual deployments should still be possible when needed.</li>
    </ul>
  </li>
  <li>All builds should upload their artifacts so they can be downloaded/inspected.</li>
</ul>

<p>The approaches we will look at are:</p>

<ol>
  <li>Place all of the build and deployment steps in a single workflow file.</li>
  <li>Have a deploy workflow listen for when the build workflow completes (<code class="language-plaintext highlighter-rouge">pull</code> approach).</li>
  <li>Have the build workflow trigger the deploy workflow (<code class="language-plaintext highlighter-rouge">push</code> approach).</li>
  <li>Have the deploy workflow include the build workflow (<code class="language-plaintext highlighter-rouge">include</code> approach).</li>
  <li>Have the deploy workflow include the build workflow, and use a template for the deployment jobs (<code class="language-plaintext highlighter-rouge">include</code> approach with reusable workflows).</li>
</ol>

<p>I typically prefer to use the <code class="language-plaintext highlighter-rouge">include</code> approach, but I’ll show each approach so you can decide which one you prefer for a given scenario.</p>

<p>If you are curious or confused about any of the workflow yaml syntax shown in the examples below, checkout <a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions">the workflow syntax for GitHub Actions docs</a>.</p>

<p>I created <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions">this sample GitHub repository</a> that contains all of the examples shown in this post, so you view their code and can see how they look in <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions">the GitHub Actions web UI</a>.</p>

<p>In the example yaml code below, I use the “👇” emoji to call out specific things to take note of, or that have changed from one approach to the next.</p>

<h2 id="approach-1-build-and-deploy-with-a-single-workflow-file">Approach 1: Build and deploy with a single workflow file</h2>

<p>Here is an example of a single workflow file that builds and deploys some code:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">1-single-file--build-and-deploy</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span> <span class="c1"># Run workflow on PRs to the main branch.</span>

  <span class="c1"># Run workflow on pushes to any branch.</span>
  <span class="na">push</span><span class="pi">:</span>

  <span class="c1"># Allows you to run this workflow manually from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-and-test</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout the repo source code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>

      <span class="c1"># Steps to version, build, and test the code go here.</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./</span> <span class="c1"># Put the path to the build artifact files directory here.</span>

  <span class="na">deploy-to-staging</span><span class="pi">:</span>
    <span class="c1"># 👇 Only run this deploy job after the build-and-test job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-and-test</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="c1"># 👇 Only run on pushes (not PRs) or manual triggers to the main branch.</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">(github.event_name == 'push' || github.event_name == 'workflow_dispatch') &amp;&amp; github.ref == 'refs/heads/main'</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>

      <span class="c1"># Steps to deploy the code go here.</span>

  <span class="na">deploy-to-production</span><span class="pi">:</span>
    <span class="c1"># 👇 Only run this deploy job after the deploy-to-staging job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">deploy-to-staging</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">environment</span><span class="pi">:</span> <span class="s">production</span> <span class="c1"># Used for environment-specific variables, secrets, and approvals.</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>

      <span class="c1"># Steps to deploy the code go here.</span>

</code></pre></div></div>

<p>There are a few things to note here.
First, the workflow is automatically triggered when a PR to the main branch is created, or when a change is pushed to any branch.
It can also be manually triggered.
Second, the workflow has 3 jobs: <code class="language-plaintext highlighter-rouge">build-and-test</code>, <code class="language-plaintext highlighter-rouge">deploy-to-staging</code>, and <code class="language-plaintext highlighter-rouge">deploy-to-production</code>.
Notice in the <code class="language-plaintext highlighter-rouge">deploy-to-staging</code> job we use a conditional <code class="language-plaintext highlighter-rouge">if</code> statement to ensure we do not deploy if the workflow was triggered by a PR, or if the <code class="language-plaintext highlighter-rouge">push</code> was not for the <code class="language-plaintext highlighter-rouge">main</code> branch.</p>

<p>Technically we did not need to create separate deploy jobs, and could have just put the deployment steps in the <code class="language-plaintext highlighter-rouge">build-and-test</code> job.
In general, it is a good idea to separate the build steps from the deployment steps to maintain a separation of concerns.
A technical reason for keeping them separate is GitHub Actions allows you to add approvals to a job via <a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idenvironment">the <code class="language-plaintext highlighter-rouge">environment</code> key</a>.
Approvals are often used to block deployments until someone manually approves it.
Only jobs support an <code class="language-plaintext highlighter-rouge">environment</code>, and you would typically have a deployment job for each environment that you need to deploy to.</p>

<p>Lastly, if you do decide to use a single job for both the build and deployment steps, then you technically do not need the <code class="language-plaintext highlighter-rouge">Upload artifact</code> and <code class="language-plaintext highlighter-rouge">Download artifact</code> steps.
I would still recommend using the <code class="language-plaintext highlighter-rouge">Upload artifact</code> step though so that the build artifact is available for download in the GitHub Actions UI, in case you need to inspect its files.</p>

<h3 id="pros-and-cons-of-using-a-single-workflow-file">Pros and cons of using a single workflow file</h3>

<p>Pros:</p>

<ul>
  <li>Simple to setup, as it is only a single file.</li>
  <li>Workflow environment variables can be easily used by all jobs.
e.g. <code class="language-plaintext highlighter-rouge">env.artifactName</code></li>
</ul>

<p>Cons:</p>

<ul>
  <li>The workflow file may quickly grow in size as more steps and jobs are added.
Having a single yaml file that is several hundred or thousands of lines long can be more difficult to maintain and daunting to look at.</li>
  <li>Workflow code changes are required to deploy from any branch other than <code class="language-plaintext highlighter-rouge">main</code>.</li>
  <li>If you want to deploy to multiple environments, you will need to duplicate the deployment steps for each environment (Approach 5 and the Reusable Workflows section below show how to solve this).
Duplicate code is harder to maintain and can easily lead to bugs if the code is not kept in sync.</li>
  <li>
    <p>The deployment jobs/steps will be skipped for PRs and branch builds, but will still show up in the workflow web UI.
This can be confusing to users, as they may not understand why those jobs/steps were skipped and that they cannot be manually triggered.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-1-jobs-that-will-never-run-show-in-the-web-ui.png" alt="Jobs that will never run showing in the web UI" /></p>
  </li>
  <li>
    <p>The PR builds and non-main branch builds will show up in the same <code class="language-plaintext highlighter-rouge">1-single-file--build-and-deploy</code> workflow runs as the <code class="language-plaintext highlighter-rouge">main</code> branch builds.
If there are a lot of PR runs or pushes to branches, they may bury the <code class="language-plaintext highlighter-rouge">main</code> branch runs, forcing you to go back several pages in the web UI to find the <code class="language-plaintext highlighter-rouge">main</code> branch runs to answer questions like, “When was the last time we deployed to production?”.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-1-main-branch-build-and-deploy-buried-under-pr-builds-in-github-workflow-runs.png" alt="Main branch build buried under PR builds" />)</p>
  </li>
</ul>

<p>To see what the GitHub Actions UI looks like with this approach, check out <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/1-single-file--build-and-deploy.yml">the workflow runs in the sample repository</a>.</p>

<h2 id="approach-2-deploy-workflow-listens-for-build-workflow-to-complete-pull-approach">Approach 2: Deploy workflow listens for build workflow to complete (Pull approach)</h2>

<p>Here is an example of a workflow file that just builds the code:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">2-pull--build</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span> <span class="c1"># Run workflow on PRs to the main branch.</span>

  <span class="c1"># Run workflow on pushes to any branch.</span>
  <span class="na">push</span><span class="pi">:</span>

  <span class="c1"># Allows you to run this workflow manually from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-and-test</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout the repo source code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>

      <span class="c1"># Steps to version, build, and test the code go here.</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./</span> <span class="c1"># Put the path to the build artifact files directory here.</span>

</code></pre></div></div>

<p>And the accompanying workflow file that deploys the code using the pull approach:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">2-pull--deploy</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># 👇 Run workflow anytime the 2-pull--build workflow completes for the main branch.</span>
  <span class="c1"># Unfortunately, can not have it only run on successful builds, so it will run when builds fail too.</span>
  <span class="na">workflow_run</span><span class="pi">:</span>
    <span class="na">workflows</span><span class="pi">:</span> <span class="s">2-pull--build</span>
    <span class="na">types</span><span class="pi">:</span> <span class="s">completed</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span>

  <span class="c1"># Allows you to run this workflow manually from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># 👇 Must specify the build artifacts to deploy when running manually.</span>
      <span class="na">workflowRunId</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">The</span><span class="nv"> </span><span class="s">build</span><span class="nv"> </span><span class="s">workflow</span><span class="nv"> </span><span class="s">run</span><span class="nv"> </span><span class="s">ID</span><span class="nv"> </span><span class="s">containing</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">artifacts</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">use.</span><span class="nv"> </span><span class="s">The</span><span class="nv"> </span><span class="s">run</span><span class="nv"> </span><span class="s">ID</span><span class="nv"> </span><span class="s">can</span><span class="nv"> </span><span class="s">be</span><span class="nv"> </span><span class="s">found</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">URL</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">build</span><span class="nv"> </span><span class="s">workflow</span><span class="nv"> </span><span class="s">run.'</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">number</span>
        <span class="na">required</span><span class="pi">:</span> <span class="no">true</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span> <span class="c1"># This must match the artifact name in the 2-pull--build workflow.</span>
  <span class="c1"># 👇 Ternary operator to use input value if manually triggered, otherwise use the workflow_run.id of the workflow run that triggered this one.</span>
  <span class="na">workflowRunId</span><span class="pi">:</span> <span class="s">${{ github.event_name == 'workflow_dispatch' &amp;&amp; inputs.workflowRunId || github.event.workflow_run.id }}</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">deploy-to-staging</span><span class="pi">:</span>
    <span class="c1"># 👇 Only run the deployment if manually triggered, or the build workflow that triggered this succeeded.</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># 👇 Must use a 3rd party action to download artifacts from other workflows.</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact from triggered workflow</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">dawidd6/action-download-artifact@v2</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">run_id</span><span class="pi">:</span> <span class="s">${{ env.workflowRunId }}</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName}}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>
          <span class="na">search_artifacts</span><span class="pi">:</span> <span class="no">true</span>

      <span class="c1"># Steps to deploy the code go here.</span>

  <span class="na">deploy-to-production</span><span class="pi">:</span>
    <span class="c1"># Only run this deploy job after the deploy-to-staging job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">deploy-to-staging</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">environment</span><span class="pi">:</span> <span class="s">production</span> <span class="c1"># Used for environment-specific variables, secrets, and approvals.</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># Must use a 3rd party action to download artifacts from other workflows.</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact from triggered workflow</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">dawidd6/action-download-artifact@v2</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">run_id</span><span class="pi">:</span> <span class="s">${{ env.workflowRunId }}</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName}}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>
          <span class="na">search_artifacts</span><span class="pi">:</span> <span class="no">true</span>

      <span class="c1"># Steps to deploy the code go here.</span>

</code></pre></div></div>

<p>Here the build workflow is separate from the deployment workflow.
The build workflow is triggered when there is a push to any branch, a PR to the main branch, or when manually triggered.
The deployment workflow uses the <code class="language-plaintext highlighter-rouge">on.workflow_run</code> trigger to wait and listen for the build workflow to complete against the main branch.</p>

<p>Because the build uses its own workflow, the deployment workflow needs a reference to the build’s workflow run ID so it knows which build run to download the artifacts from.
This is provided automatically when the build triggers the deployment workflow, but must be provided manually when the deployment workflow is manually triggered.
You can find the build workflow run ID by opening the build workflow run in the GitHub Actions UI and looking at the URL.
The URL will look something like <code class="language-plaintext highlighter-rouge">https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/runs/6985605790</code>, where the run ID is <code class="language-plaintext highlighter-rouge">6985605790</code>.</p>

<p>The next thing to note is that the <code class="language-plaintext highlighter-rouge">artifactName</code> environment variable is duplicated in both the build and deployment workflows.
We could have the build workflow create an output variable that the deployment workflow could reference, but for the sake of simplicity I just duplicated the environment variable here.</p>

<p>Next, notice that the <code class="language-plaintext highlighter-rouge">deploy-to-staging</code> job has a conditional <code class="language-plaintext highlighter-rouge">if</code> statement that will only run the job if the workflow was manually triggered, or if the build workflow completed successfully.
Unfortunately, at this time, <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run">the <code class="language-plaintext highlighter-rouge">on.workflow_run</code> event</a> does not have a property to indicate that the deploy workflow should only be triggered if the build workflow completed successfully, so we have to do the check ourselves on the job.</p>

<p>Lastly, the deployment jobs use a 3rd party action to download the build artifact.
At this time, GitHub Actions does not have a built-in action to download artifacts from other workflows.
They do provide <a href="https://stackoverflow.com/a/77009805/602585">API endpoints to download artifacts from other workflows</a>, but it is simpler to use the 3rd party action.</p>

<h3 id="pros-and-cons-of-using-the-pull-approach">Pros and cons of using the pull approach</h3>

<p>Pros:</p>

<ul>
  <li>The build and deployment steps are separated into their own workflows, making them easier to maintain and understand.</li>
  <li>
    <p>The build workflow only shows the build steps in the GitHub Actions UI, and the deployment workflow only shows the deployment steps, so we do not constantly have “skipped” jobs for non-deployment builds like in the previous single-workflow-file approach.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-2-workflows-do-not-need-to-have-skipped-steps.png" alt="Workflows only contain appropriate steps so they do not always have skipped steps" /></p>
  </li>
  <li>Non-main branches and PRs can be manually deployed without any workflow code changes.</li>
  <li>Builds for PRs and non-main branches that we do not want deployed do not trigger deployment workflows.</li>
</ul>

<p>Cons:</p>

<ul>
  <li>
    <p>The deployment workflow is triggered even if the build workflow for the main branch fails, resulting in skipped deployment runs showing in the workflow UI.
This can be confusing to users, and it clutters the workflow UI.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-2-deploy-workflow-jobs-are-all-skipped-when-main-branch-build-fails.png" alt="Skipped deployment workflow runs clutter the workflow UI" /></p>
  </li>
  <li>
    <p>The name of the deployment workflow run is always <code class="language-plaintext highlighter-rouge">2-pull--deploy</code>, rather than the commit message of the build workflow run that triggered it.
It also does not show the commit SHA.
This can make it difficult to find the deployment workflow run you are looking for in the GitHub UI.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-2-deploy-workflow-run-name-is-always-the-same.png" alt="Deployment workflow runs are always named the same and do not include commit SHA" /></p>
  </li>
  <li>
    <p>Certain variables must be duplicated between the build and deployment workflows (e.g. <code class="language-plaintext highlighter-rouge">env.artifactName</code>), or additional code added to pass the variables between the workflows.</p>
  </li>
</ul>

<p>To see what the GitHub Actions UI looks like with this approach, check out the workflows in the sample repository for the <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/2-pull--build.yml">pull build runs</a> and <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/2-pull--deploy.yml">pull deploy runs</a>.</p>

<h3 id="similarities-to-azure-devops-classic-pipelines">Similarities to Azure DevOps classic pipelines</h3>

<p>After using Azure DevOps classic pipelines for years, this approach felt very natural and is probably the one most similar to classic pipelines.
In Azure DevOps, you would explicitly create separate build and deployment pipelines, and the first step of the deployment pipeline setup is specifying the build pipeline that it should pull the artifacts from, and potentially automatically trigger off of.
So the build pipeline would not know anything about the deployment pipeline, and the deployment pipeline could be automatically triggered when the build pipeline completed successfully.</p>

<p>This approach worked quite well in GitHub at first, but I really did not like how blank, skipped deployment workflow runs got created when the main branch build failed.
It quickly cluttered up the deployment runs when issues were encountered with the build workflow that took many attempts to fix.
Also, having every deployment run named the same thing made finding a specific deployment very painful.</p>

<h3 id="listening-for-other-events-to-trigger-the-deployment-workflow">Listening for other events to trigger the deployment workflow</h3>

<p>Rather than triggering the deployment workflow when a build workflow completes, you may want to trigger the deployment workflow on other events, such as when a new image is uploaded to a container registry, or when a new release is created.
These are valid scenarios that may be suitable for your project.
One thing to consider is that you will either need to no longer upload the artifact to the container registry for non-main branch builds, meaning you are not able to inspect and test them, or you will need to add code to your deployment workflow to determine if the artifact should be deployed or not.
This may or may not be a big deal, depending on your project requirements.</p>

<h2 id="approach-3-build-workflow-triggers-deploy-workflow-push-approach">Approach 3: Build workflow triggers deploy workflow (Push approach)</h2>

<p>Here is an example of a workflow that builds the code and then triggers a deployment workflow:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">3-push--build</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span> <span class="c1"># Run workflow on PRs to the main branch.</span>

  <span class="c1"># Run workflow on pushes to any branch.</span>
  <span class="na">push</span><span class="pi">:</span>

  <span class="c1"># Allows you to run this workflow manually from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># 👇 Allow deploying non-main branch builds.</span>
      <span class="na">deploy</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Deploy</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">build</span><span class="nv"> </span><span class="s">artifacts.</span><span class="nv"> </span><span class="s">Only</span><span class="nv"> </span><span class="s">has</span><span class="nv"> </span><span class="s">effect</span><span class="nv"> </span><span class="s">when</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">building</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">main</span><span class="nv"> </span><span class="s">branch.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="no">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">boolean</span>
        <span class="na">default</span><span class="pi">:</span> <span class="no">false</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-and-test</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout the repo source code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>

      <span class="c1"># Steps to version, build, and test the code go here.</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./</span> <span class="c1"># Put the path to the build artifact files directory here.</span>

  <span class="c1"># 👇 Trigger the deployment workflow.</span>
  <span class="na">trigger-deployment</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-and-test</span>
    <span class="c1"># 👇 Only trigger a deployment if the deploy parameter was set, or this build is for a push (not a PR) on the default branch (main).</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">inputs.deploy || (github.event_name != 'pull_request' &amp;&amp; github.ref == format('refs/heads/{0}', github.event.repository.default_branch))</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/3-push--deploy.yml</span>
    <span class="c1"># 👇 Allow the deployment workflow to access the secrets of this workflow.</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>

</code></pre></div></div>

<p>And here is the accompanying deployment workflow:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">3-push--deploy</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># 👇 Allow this workflow to be called by the 3-push--build workflow.</span>
  <span class="na">workflow_call</span><span class="pi">:</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span> <span class="c1"># This must match the artifact name in the 3-push--build workflow.</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">deploy-to-staging</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># 👇 Can use the native download-artifact action.</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>

      <span class="c1"># Steps to deploy the code go here.</span>

  <span class="na">deploy-to-production</span><span class="pi">:</span>
    <span class="c1"># Only run this deploy job after the deploy-to-staging job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">deploy-to-staging</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">environment</span><span class="pi">:</span> <span class="s">production</span> <span class="c1"># Used for environment-specific variables, secrets, and approvals.</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>

      <span class="c1"># Steps to deploy the code go here.</span>

</code></pre></div></div>

<p>Once again the build workflow is separate from the deployment workflow.
The build workflow will trigger on a push to any branch, PRs to the main branch, or when manually triggered.
Rather than the deploy workflow listening for the build workflow to complete, the build workflow explicitly calls the deploy workflow as its final job.</p>

<p>Looking at the <code class="language-plaintext highlighter-rouge">trigger-deployment</code> job, we can see that only builds made from the main branch will trigger the deployment workflow.
A <code class="language-plaintext highlighter-rouge">deploy</code> parameter is also provided in the build workflow that can be set when manually triggering a build, allowing for non-main branch builds to be deployed as well, if needed.
Notice that the job provides the <code class="language-plaintext highlighter-rouge">secrets: inherit</code> key, which allows the deployment workflow to access the secrets of the build workflow.
Without this, the deployment workflow would not have access to the GitHub repository secrets.</p>

<blockquote>
  <p>Aside: In addition to passing secrets to the deployment workflow, you can also pass other parameters to the deployment workflow by using the <code class="language-plaintext highlighter-rouge">with</code> key.
While none are shown in this example, I will mention that in order to pass non-string values (e.g. boolean or number), I had to use the <code class="language-plaintext highlighter-rouge">fromJSON</code> function to maintain the variable’s type, as shown in <a href="https://github.com/actions/runner/issues/2206#issuecomment-1532246677">this GitHub issue</a>.</p>
</blockquote>

<p>Looking at the deployment workflow, you can see we are using the <code class="language-plaintext highlighter-rouge">on: workflow_call</code> event to allow the workflow to be called by the build workflow.
Since the build workflow is triggering the deployment workflow, the end result is a single workflow run, meaning we can use the native <code class="language-plaintext highlighter-rouge">actions/download-artifact</code> action to download the build artifact, rather than having to use a 3rd party action.</p>

<h3 id="pros-and-cons-of-using-the-push-approach">Pros and cons of using the push approach</h3>

<p>Pros:</p>

<ul>
  <li>The build and deployment steps are separated into their own workflows, making them easier to maintain and understand.</li>
  <li>Non-main branches and PRs can be manually deployed without any workflow code changes.</li>
  <li>Builds for PRs and non-main branches that we do not want deployed do not trigger deployment workflows.</li>
</ul>

<p>Cons:</p>

<ul>
  <li>
    <p>Since the deployment workflow is never triggered by GitHub, but is instead called by the build workflow, it means the deployment workflow will never show any runs.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-3-deployment-workflow-never-has-any-runs.png" alt="Deployment workflow never has any runs" /></p>

    <p>Instead, the deploy jobs will show up as part of the build workflow run.
This means that builds for PRs and non-main branches will be mixed in with the <code class="language-plaintext highlighter-rouge">main</code> branch builds and deployments.
Just like with the <code class="language-plaintext highlighter-rouge">1-single-file--build-and-deploy</code> approach, this may bury the deployments under several pages of non-main branch runs, making it difficult to find runs you care about in the GitHub UI.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-3-all-builds-and-deployments-are-mixed-in-together.png" alt="Builds and deployments for all branches are mixed in together" /></p>
  </li>
  <li>
    <p>Since the deployment jobs show up in the build workflow run, the GitHub UI prefixes each of the deployment jobs with the name of the job from the build workflow.
This can make it difficult to see the full name of the deployment jobs in the generated diagram.
Thankfully, the jobs are listed on the left in a tree view as well, so you can still read the full name there more easily.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-3-build-job-prefixed-on-deployment-job-names.png" alt="Deployment workflow name is prefixed on deployment jobs" /></p>
  </li>
</ul>

<p>To see what the GitHub Actions UI looks like with this approach, check out the workflows in the sample repository for the <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/3-push--build.yml">push build runs</a> and <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/3-push--deploy.yml">push deploy runs</a>.</p>

<h2 id="approach-4-deploy-workflow-includes-build-workflow-include-approach">Approach 4: Deploy workflow includes build workflow (Include approach)</h2>

<p>Here is an example of a workflow that builds the code:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">4-include--build</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span> <span class="c1"># Run workflow on PRs to the main branch.</span>

  <span class="c1"># 👇 Run workflow on pushes to any branch, except the main branch.</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches-ignore</span><span class="pi">:</span> <span class="s">main</span>

  <span class="c1"># Allows you to run this workflow manually from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

  <span class="c1"># 👇 Allows this workflow to be called from the deployment workflow.</span>
  <span class="na">workflow_call</span><span class="pi">:</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-and-test</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout the repo source code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>

      <span class="c1"># Steps to version, build, and test the code go here.</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./</span> <span class="c1"># Put the path to the build artifact files directory here.</span>

</code></pre></div></div>

<p>And here is the accompanying deployment workflow:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">4-include--deploy</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># 👇 Trigger the workflow on a push to the main branch.</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span>

  <span class="c1"># 👇 Allows you to run this workflow manually (for any branch) from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span> <span class="c1"># This must match the artifact name in the 4-include--build workflow.</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># 👇 Call the build workflow to create the artifacts to deploy.</span>
  <span class="na">build-and-test</span><span class="pi">:</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/4-include--build.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span> <span class="c1"># Pass secrets to the build workflow, if necessary.</span>

  <span class="na">deploy-to-staging</span><span class="pi">:</span>
    <span class="c1"># 👇 Only run this deploy job after the build-and-test job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-and-test</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>

      <span class="c1"># Steps to deploy the code go here.</span>

  <span class="na">deploy-to-production</span><span class="pi">:</span>
    <span class="c1"># Only run this deploy job after the deploy-to-staging job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">deploy-to-staging</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">environment</span><span class="pi">:</span> <span class="s">production</span> <span class="c1"># Used for environment-specific variables, secrets, and approvals.</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>

      <span class="c1"># Steps to deploy the code go here.</span>

</code></pre></div></div>

<p>You will notice that we still have separate build and deployment workflows.
One key difference here is the specific triggers for each workflow.
They have been set up so that the build workflow is only triggered by non-deployment builds, and the deployment workflow is triggered by builds that are meant to be deployed.</p>

<p>The build workflow will trigger on a push to any branch <em>EXCEPT</em> the main branch, PRs to the main branch, or when manually triggered.
It also allows other workflows to call it via the <code class="language-plaintext highlighter-rouge">workflow_call</code> event.
The build workflow no longer triggers deployments, neither directly (push) nor indirectly (pull).</p>

<p>The deployment workflow will now trigger on a push to the main branch, or when manually triggered.
The manual trigger allows non-main branch deployments if needed.
A key thing to note here is that the deployment workflow includes the build workflow via the <code class="language-plaintext highlighter-rouge">uses</code> key, so when a deployment is triggered it will first run the build jobs as part of its workflow run.
This is similar to the <code class="language-plaintext highlighter-rouge">push</code> approach mentioned earlier, except that the dependency has been inverted so instead of the build workflow calling the deployment workflow, the deployment workflow calls the build workflow.
This improves the workflow UI experience, as the deployment jobs will show up as part of the deployment workflow run, rather than the build workflow run like they did with the push approach.</p>

<p>I came across this approach on <a href="https://www.viget.com/articles/automating-build-deploy-ci-cd-with-github-actions/">this excellent blog post</a> when I wasn’t satisfied with the pull and push approaches.</p>

<h3 id="pros-and-cons-of-using-the-include-approach">Pros and cons of using the include approach</h3>

<p>Pros:</p>

<ul>
  <li>Builds for PRs and non-main branches that we do not want deployed do not trigger deployment workflows, and show up in the GitHub UI under the build workflow.
These are typically throwaway builds, so it is nice to not have them cluttering up the deployment workflow runs.</li>
  <li>Builds that are deployed show up in the GitHub UI under the deployment workflow, making it easy to find the last time a deployment occurred.</li>
  <li>Non-main branches and PRs can be manually deployed without any workflow code changes.</li>
</ul>

<p>Cons:</p>

<ul>
  <li>
    <p>In the deployment workflow run GitHub UI, the build job name is prefixed with the name of the build job in the deployment workflow, which is a minor annoyance.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-4-build-job-name-is-prefixed-with-deployment-workflow-job-name.png" alt="Build job name prefixed with job name from deployment workflow" /></p>
  </li>
</ul>

<p>To see what the GitHub Actions UI looks like with this approach, check out the workflows in the sample repository for the <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/4-include--build.yml">include build runs</a> and <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/4-include--deploy.yml">include deploy runs</a>.</p>

<h2 id="reusable-workflows-templates">Reusable workflows (templates)</h2>

<p>You probably noticed that we are always deploying to 2 environments: staging and production.
You may have even more environments that you need to deploy to.
This results in a lot of duplicate code in the deployment workflows.</p>

<p>To avoid the duplicate code, we can use a reusable workflow to define the deployment jobs, and then include the reusable workflow in the deployment workflow.
Azure DevOps calls these “templates”, but GitHub Actions calls them “reusable workflows”.
You can think of a reusable workflow as a function that accepts parameters, so you define it once, and then can call it multiple times with different parameters.</p>

<p>One caveat to be aware of is that while templates may also include other templates, GitHub only allows up to 4 levels of template nesting.
Also, a workflow may only call up to 20 other workflows, including nested ones.
And just like regular workflows, reusable workflows must be placed directly in the <code class="language-plaintext highlighter-rouge">.github/workflows</code> directory.</p>

<p>See the <a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows">GitHub docs on reusable workflows</a> for more information and limitations.</p>

<p>Although I am only now introducing reusable workflows here, we’ve actually already been using them in the <code class="language-plaintext highlighter-rouge">push</code> and <code class="language-plaintext highlighter-rouge">include</code> approaches above, but were not calling them multiple times.
Let’s see how to do that now.</p>

<h2 id="approach-5-deploy-workflow-includes-build-workflow-and-uses-template-for-deployments-include-approach-with-reusable-workflows">Approach 5: Deploy workflow includes build workflow, and uses template for deployments (Include approach with reusable workflows)</h2>

<p>Here is an example of a workflow that builds the code:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">5-include-with-deploy-template--build</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span> <span class="c1"># Run workflow on PRs to the main branch.</span>

  <span class="c1"># Run workflow on pushes to any branch, except the main branch.</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches-ignore</span><span class="pi">:</span> <span class="s">main</span>

  <span class="c1"># Allows you to run this workflow manually from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

  <span class="c1"># 👇 Allows this workflow to be called from the deployment workflow, but the parameters must be provided.</span>
  <span class="na">workflow_call</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">artifactName</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s">The name of the artifact to upload to.</span>
        <span class="na">required</span><span class="pi">:</span> <span class="no">true</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="c1"># 👇 Provide a default artifact name for when this workflow is not called by the deployment workflow.</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">${{ inputs.artifactName || 'buildArtifact' }}</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build-and-test</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout the repo source code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>

      <span class="c1"># Steps to version, build, and test the code go here.</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ env.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./</span> <span class="c1"># Put the path to the build artifact files directory here.</span>

</code></pre></div></div>

<p>And here is the accompanying deployment workflow:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">5-include-with-deploy-template--deploy</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Trigger the workflow on a push to the main branch.</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="s">main</span>

  <span class="c1"># Allows you to run this workflow manually (for any branch) from the Actions tab.</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="c1"># 👇 Set the artifact name that will be used by the build and deployments, so it is now only defined in one place.</span>
  <span class="na">artifactName</span><span class="pi">:</span> <span class="s">buildArtifact</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># 👇 Call the build workflow to create the artifacts to deploy, and provide the artifact name.</span>
  <span class="na">build-and-test</span><span class="pi">:</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/5-include-with-deploy-template--build.yml</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">artifactName</span><span class="pi">:</span> <span class="s">${{ github.env.artifactName }}</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span> <span class="c1"># Pass secrets to the build workflow, if necessary.</span>

  <span class="na">deploy-to-staging</span><span class="pi">:</span>
    <span class="c1"># Only run this deploy job after the build-and-test job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build-and-test</span>
    <span class="c1"># 👇 Call the deploy template with the proper environment name to deploy the artifacts.</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/5-include-with-deploy-template--deploy-template.yml</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">artifactName</span><span class="pi">:</span> <span class="s">${{ github.env.artifactName }}</span>
      <span class="na">environmentName</span><span class="pi">:</span> <span class="s">staging</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span> <span class="c1"># Pass repository secrets to the deployment workflow.</span>

  <span class="na">deploy-to-production</span><span class="pi">:</span>
    <span class="c1"># Only run this deploy job after the deploy-to-staging job completes successfully.</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">deploy-to-staging</span>
    <span class="c1"># 👇 Call the deploy template with the proper environment name to deploy the artifacts.</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/5-include-with-deploy-template--deploy-template.yml</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">artifactName</span><span class="pi">:</span> <span class="s">${{ github.env.artifactName }}</span>
      <span class="na">environmentName</span><span class="pi">:</span> <span class="s">production</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span> <span class="c1"># Pass repository secrets to the deployment workflow.</span>

</code></pre></div></div>

<p>We now have on additional workflow file, which is the reusable workflow (template) that defines the deployment job:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="na">name</span><span class="pi">:</span> <span class="s">5-include-with-deploy-template--deploy-template</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># 👇 Allows this workflow to be called from the deployment workflow, but the parameters must be provided.</span>
  <span class="na">workflow_call</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">artifactName</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s">The name of the artifact to download and deploy.</span>
        <span class="na">required</span><span class="pi">:</span> <span class="no">true</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="na">environmentName</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s">The name of the environment to deploy to.</span>
        <span class="na">required</span><span class="pi">:</span> <span class="no">true</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">deploy</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="c1"># 👇 Allows using variables and secrets defined in the provided environment.</span>
    <span class="na">environment</span><span class="pi">:</span> <span class="s">${{ inputs.environmentName }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">${{ inputs.artifactName }}</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./buildArtifact</span>

      <span class="c1"># Steps to deploy the code go here.</span>

</code></pre></div></div>

<p>I could have left the build workflow identical to the <code class="language-plaintext highlighter-rouge">include</code> approach shown earlier, however I thought that I would show how to allow the artifact name to be provided as a parameter.
This allows the deployment workflow to provide the artifact name to the build workflow, so that we do not have to hardcode the value in 2 separate files as we had been doing earlier.
Since the build may still be triggered outside of a deployment workflow, we had to update the <code class="language-plaintext highlighter-rouge">env: artifactName</code> to use a default value when the input value is not provided.
This same approach can be used for other values that you want shared between different workflows.
<a href="https://7tonshark.com/posts/github-actions-ternary-operator/">This excellent blog post</a> shows how to also to use the ternary operator to provide a default value, and also explains why we cannot use <code class="language-plaintext highlighter-rouge">env</code> variables in a job’s <code class="language-plaintext highlighter-rouge">if</code> clause.</p>

<p>Next we see the deployment workflow.
As mentioned above, you can see that we now pass the artifact name to the build workflow, so the only place the artifact name value is defined is in the deployment workflow.
Next, notice that the workflow no longer duplicates the deployment steps in the <code class="language-plaintext highlighter-rouge">deploy-to-staging</code> and <code class="language-plaintext highlighter-rouge">deploy-to-production</code> jobs, but instead calls the new reusable workflow with the appropriate parameters; namely the <code class="language-plaintext highlighter-rouge">environmentName</code>.
In our example the deployment code was simply <code class="language-plaintext highlighter-rouge"># Steps to deploy the code go here.</code>, but in a real world scenario the deployment steps may be several hundred lines of yaml code, so not duplicating it is a big win.</p>

<p>Finally, look at the reusable workflow.
You can see it takes 2 parameters: <code class="language-plaintext highlighter-rouge">artifactName</code> and <code class="language-plaintext highlighter-rouge">environmentName</code>.
It defines a single job that is used to perform the deployment.</p>

<p>GitHub allows you to create environment-specific variables and secrets, which can be used by the jobs in the workflow.
These are configured in the GitHub repository web UI under the “Settings” tab, and then the “Environments” menu item.
I personally prefer to have the variable values defined directly in the workflow files so that they are under source control.
If you take that approach, you would simply add additional parameters to the reusable workflow and pass them in from the deployment workflow, just like we did with the <code class="language-plaintext highlighter-rouge">artifactName</code> and <code class="language-plaintext highlighter-rouge">environmentName</code> parameters.
Secrets of course should not be committed to source control, so you would still want to define those in the GitHub UI, or use a 3rd party secret manager like Azure Key Vault.</p>

<h3 id="pros-and-cons-of-using-reusable-workflows-templates">Pros and cons of using reusable workflows (templates)</h3>

<p>This approach has the same pros and cons as the <code class="language-plaintext highlighter-rouge">include</code> approach above, as well as:</p>

<p>Pros:</p>

<ul>
  <li>The deployment job is only defined once so there is no duplicated code.</li>
</ul>

<p>Cons:</p>

<ul>
  <li>
    <p>Similar to <code class="language-plaintext highlighter-rouge">3-push--deploy</code> above, because the template will only be referenced by other workflows and never directly triggered itself, it still shows up in the GitHub Actions UI as a workflow, but will never have any runs shown, which may be confusing to users.</p>

    <p><img src="/assets/Posts/2024-01-05-Multiple-ways-to-setup-your-CI-CD-pipelines-in-GitHub-Actions/approach-5-deploy-template-will-never-show-any-runs.png" alt="Deploy template shows in the workflows even though it will never have any runs" /></p>
  </li>
</ul>

<p>To see what the GitHub Actions UI looks like with this approach, check out the workflows in the sample repository for the <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/5-include-with-deploy-template--build.yml">build runs</a> and <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions/actions/workflows/5-include-with-deploy-template--deploy.yml">deploy runs</a>.</p>

<p>GitHub does not allow you to place reusable workflows in subdirectories, so they are mixed in with other workflow files.
It is a good idea is to prefix them with a word like <code class="language-plaintext highlighter-rouge">template</code> to make it clear that they are reusable workflows, and not meant to be triggered directly.</p>

<p>When using the <code class="language-plaintext highlighter-rouge">include</code> approach and this one, you probably do not want to name your workflow just <code class="language-plaintext highlighter-rouge">deploy</code>, since it actually both builds and deploys.
You may want to name the workflows something like <code class="language-plaintext highlighter-rouge">build-and-test</code> and <code class="language-plaintext highlighter-rouge">build-test-and-deploy</code> to more accurately reflect what they do, and call the deployment reusable workflow something like <code class="language-plaintext highlighter-rouge">template-deploy</code>.</p>

<p>This is the approach I typically use for my projects.</p>

<h2 id="conclusion">Conclusion</h2>

<p>In this post we’ve seen a number of different approach you can take to define your build and deploy workflows.
I typically prefer the last <code class="language-plaintext highlighter-rouge">include with reusable workflows</code> approach, but you may prefer another depending on your needs.</p>

<p>The example code I’ve shown here is very simple and should simply be used as a starting point.
You may want additional triggers for your workflows, such as when a tag is created, or on a schedule.
You may want to create additional templates for other operations, such as running load tests, or running a security scan.</p>

<p>My goal with this post was to share the various ways I’ve found to structure GitHub CI/CD workflows, and hopefully give you some ideas on how to structure your own.</p>

<p>Finally, a reminder that I also created <a href="https://github.com/deadlydog/GitHub.Experiment.CiCdApproachesWithGitHubActions">this sample GitHub repository</a> that contains all of the examples shown in this post, so you view the code and can see how they look in the GitHub Actions menu.</p>

<p>Have another approach that you think should be on this list, or other relevant information?
Leave a comment and let me know.</p>

<p>Happy pipeline building!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="GitHub" /><category term="Build" /><category term="Deploy" /><category term="GitHub" /><category term="Build" /><category term="Deploy" /><category term="GitHub Actions" /><category term="Continuous Integration" /><category term="Continuous Deployment" /><summary type="html"><![CDATA[In this post I’ll show different approaches to setting up your build and deployment workflows with GitHub Actions, as well as some pros and cons of each.]]></summary></entry><entry><title type="html">First, Single, and SingleOrDefault in .NET are harmful</title><link href="https://blog.danskingdom.com/First-Single-and-SingleOrDefault-in-dotnet-are-harmful/" rel="alternate" type="text/html" title="First, Single, and SingleOrDefault in .NET are harmful" /><published>2023-10-03T00:00:00+00:00</published><updated>2023-10-03T00:00:00+00:00</updated><id>https://blog.danskingdom.com/First-Single-and-SingleOrDefault-in-dotnet-are-harmful</id><content type="html" xml:base="https://blog.danskingdom.com/First-Single-and-SingleOrDefault-in-dotnet-are-harmful/"><![CDATA[<p>I’ve wasted countless hours troubleshooting exceptions thrown by the .NET <code class="language-plaintext highlighter-rouge">First</code>, <code class="language-plaintext highlighter-rouge">Single</code>, and <code class="language-plaintext highlighter-rouge">SingleOrDefault</code> methods.
In this post we’ll look at why these methods can be harmful, and what you should do instead.</p>

<h2 id="tldr">TL;DR</h2>

<p><code class="language-plaintext highlighter-rouge">Single</code>, <code class="language-plaintext highlighter-rouge">SingleOrDefault</code>, and <code class="language-plaintext highlighter-rouge">First</code> throw exceptions that are vague and unhelpful.
You are better off to write the logic yourself and include rich error/log information that will help with troubleshooting.</p>

<h2 id="what-is-single-singleordefault-first-and-firstordefault">What is Single, SingleOrDefault, First, and FirstOrDefault</h2>

<p><code class="language-plaintext highlighter-rouge">Single</code>, <code class="language-plaintext highlighter-rouge">SingleOrDefault</code>, <code class="language-plaintext highlighter-rouge">First</code>, and <code class="language-plaintext highlighter-rouge">FirstOrDefault</code> are LINQ extension methods that return the first element of a sequence that satisfies a specified condition.
The typical use cases for these methods are:</p>

<ul>
  <li>Use <code class="language-plaintext highlighter-rouge">Single</code> to retrieve an item, ensuring that one and only one item matches the condition.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">SingleOrDefault</code> to retrieve an item, and ensure that it is the only one that matches the condition.
If no items match the condition a default item is returned.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">First</code> to retrieve the first item, and throw an exception if no matches are found.
It does not care if multiple items match the condition.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">FirstOrDefault</code> to retrieve the first item.
If no items match the condition a default item is returned, and it does not care if multiple items match the condition.</li>
</ul>

<p>Here is an example of using them in C#:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">people</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="p">{</span> <span class="s">"Alfred Archer"</span><span class="p">,</span> <span class="s">"Billy Baller"</span><span class="p">,</span> <span class="s">"Billy Bob"</span><span class="p">,</span> <span class="s">"Cathy Carter"</span> <span class="p">};</span>
<span class="p">---</span>
<span class="kt">var</span> <span class="n">alfred</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">First</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Alfred"</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">billy</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">First</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Billy"</span><span class="p">));</span> <span class="c1">// Returns Billy Baller</span>
<span class="kt">var</span> <span class="n">zane</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">First</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Zane"</span><span class="p">));</span> <span class="c1">// Throws an exception</span>
<span class="p">---</span>
<span class="kt">var</span> <span class="n">alfred</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Alfred"</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">billy</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Billy"</span><span class="p">));</span> <span class="c1">// Returns Billy Baller</span>
<span class="kt">var</span> <span class="n">zane</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Zane"</span><span class="p">));</span> <span class="c1">// Returns null</span>
<span class="p">---</span>
<span class="kt">var</span> <span class="n">alfred</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">Single</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Alfred"</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">billy</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">Single</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Billy"</span><span class="p">));</span> <span class="c1">// Throws an exception</span>
<span class="kt">var</span> <span class="n">zane</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">Single</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Zane"</span><span class="p">));</span> <span class="c1">// Throws an exception</span>
<span class="p">---</span>
<span class="kt">var</span> <span class="n">alfred</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">SingleOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Alfred"</span><span class="p">));</span>
<span class="kt">var</span> <span class="n">billy</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">SingleOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Billy"</span><span class="p">));</span> <span class="c1">// Throws an exception</span>
<span class="kt">var</span> <span class="n">zane</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">SingleOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"Zane"</span><span class="p">));</span> <span class="c1">// Returns null</span>
</code></pre></div></div>

<h2 id="why-first-single-and-singleordefault-are-harmful">Why First, Single, and SingleOrDefault are harmful</h2>

<p>When <code class="language-plaintext highlighter-rouge">First</code> and <code class="language-plaintext highlighter-rouge">Single</code> do not find any items that match the condition, they throw the following exception:</p>

<blockquote>
  <p>System.InvalidOperationException: Sequence contains no matching element</p>
</blockquote>

<p>When <code class="language-plaintext highlighter-rouge">Single</code> and <code class="language-plaintext highlighter-rouge">SingleOrDefault</code> find more than one element that satisfies the condition, they throw the following exception:</p>

<blockquote>
  <p>System.InvalidOperationException: Sequence contains more than one matching element</p>
</blockquote>

<p>Both of these exception messages are vague and unhelpful.
Imagine seeing it in your application logs, or worse, returned to the user.
Would you know right away what the problem was and how to fix it?
Even with a stack trace, which we do not always have, it may not be obvious.</p>

<p>The message doesn’t tell us what dataset was searched, what the condition was, or what elements satisfied the condition.
This is crucial information to know when troubleshooting what went wrong.</p>

<p>Not only is the exception message unhelpful, but throwing an exception is an expensive operation that is best to avoid when possible.</p>

<p><code class="language-plaintext highlighter-rouge">FirstOrDefault</code> is the only method that does not throw an exception, and thus is the only one that I would recommend using.</p>

<h2 id="so-should-i-just-use-firstordefault-instead">So should I just use FirstOrDefault instead?</h2>

<p>Simply using <code class="language-plaintext highlighter-rouge">FirstOrDefault</code> instead of <code class="language-plaintext highlighter-rouge">SingleOrDefault</code> is not a good solution.
If you were considering using <code class="language-plaintext highlighter-rouge">SingleOrDefault</code>, then you are probably trying to validate that only a single item was returned, as more than one item returned may mean that something is wrong with your query, your data, your code, or your business logic.
If we just use <code class="language-plaintext highlighter-rouge">FirstOrDefault</code> instead, it hides the issue that multiple items matched the search criteria and we may never realize that there is a problem.</p>

<p>In our example above, when using <code class="language-plaintext highlighter-rouge">FirstOrDefault</code> the <code class="language-plaintext highlighter-rouge">billy</code> variable would be set to “Billy Baller” and there would be no indication that there was another “Billy” in the dataset.
This could lead to problems if our logic expects there to only be one “Billy” in the dataset and makes decisions based on that assumption.</p>

<p>Similarly for <code class="language-plaintext highlighter-rouge">First</code> and <code class="language-plaintext highlighter-rouge">Single</code>, simply using <code class="language-plaintext highlighter-rouge">FirstOrDefault</code> in their place without additional validation to ensure that a result was found is a recipe for disaster.</p>

<h2 id="so-what-should-i-do-instead">So what should I do instead?</h2>

<p>One solution is to use <code class="language-plaintext highlighter-rouge">Where</code> instead of <code class="language-plaintext highlighter-rouge">Single</code> and <code class="language-plaintext highlighter-rouge">SingleOrDefault</code>, and then explicitly validate that only a single item was returned.
This allows us to return a rich error message, as well as avoid the expensive exception being thrown if we want.</p>

<p>Here is one example of how we could do this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">people</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="p">{</span> <span class="s">"Alfred Archer"</span><span class="p">,</span> <span class="s">"Billy Baller"</span><span class="p">,</span> <span class="s">"Billy Bob"</span><span class="p">,</span> <span class="s">"Cathy Carter"</span> <span class="p">};</span>

<span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="s">"Billy"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">persons</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">)).</span><span class="nf">ToList</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">persons</span><span class="p">.</span><span class="n">Count</span> <span class="p">&gt;</span> <span class="m">1</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">TooManyPeopleFoundException</span><span class="p">(</span>
      <span class="s">$"Expected to only find one person named '</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s">', but found </span><span class="p">{</span><span class="n">persons</span><span class="p">.</span><span class="n">Count</span><span class="p">}</span><span class="s"> in our people list: </span><span class="p">{</span><span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span> <span class="n">persons</span><span class="p">)}</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You can see that the exception thrown contains much more information to help troubleshoot the problem, such as:</p>

<ul>
  <li>We used a specific type of exception, <code class="language-plaintext highlighter-rouge">TooManyPeopleFoundException</code>.</li>
  <li>We include the condition that was used to find the people. e.g. person named {name}.</li>
  <li>We include the number of items found. e.g. {persons.Count}</li>
  <li>We include the data source that was searched. e.g. our people list.</li>
  <li>We include all of the matching items that were found. e.g. Billy Baller, Billy Bob.</li>
</ul>

<p>If we also wanted to ensure that at least one item was found, we could add the following check:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">persons</span><span class="p">.</span><span class="n">Count</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">PersonNotFoundException</span><span class="p">(</span><span class="s">$"Could not find a person named '</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s">' in our people list."</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is just one example of how you could implement this.
You might choose to create your own SingleItemOrDefault helper method or extension method that performs the operations and adds the information to the exception.
You might not want to throw an exception at all, but instead use the Result pattern to return a failed result with the rich information.</p>

<p>The above shows how to avoid using <code class="language-plaintext highlighter-rouge">Single</code> and <code class="language-plaintext highlighter-rouge">SingleOrDefault</code>.
Let’s see how we can avoid using <code class="language-plaintext highlighter-rouge">First</code> as well.</p>

<p>Rather than using <code class="language-plaintext highlighter-rouge">First</code> we can leverage <code class="language-plaintext highlighter-rouge">FirstOrDefault</code>.
<code class="language-plaintext highlighter-rouge">FirstOrDefault</code> is preferred over <code class="language-plaintext highlighter-rouge">Where(...).ToList()</code> and checking <code class="language-plaintext highlighter-rouge">Count == 0</code> to avoid iterating over the entire collection when a match exists.
Here’s an example of using <code class="language-plaintext highlighter-rouge">FirstOrDefault</code> instead of <code class="language-plaintext highlighter-rouge">First</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="s">"Zane"</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">person</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">FirstOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">person</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">PersonNotFoundException</span><span class="p">(</span><span class="s">$"Could not find a person named '</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s">' in our people list."</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You will need to be mindful of the default value for the type you are working with.
Object types will be null, but value types will be their default value.</p>

<p>The important thing is that you do not rely on the default <code class="language-plaintext highlighter-rouge">InvalidOperationException</code>, and that your error message includes all the information that will help you troubleshoot any issues.
This is a good general rule to follow for any error logging you perform.
Depending on the sensitivity of the data, you may need to be careful about which information you include in the error.</p>

<h2 id="why-dont-i-just-catch-the-exception-and-add-information">Why don’t I just catch the exception and add information?</h2>

<p>While you could do something like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="s">"Billy"</span><span class="p">;</span>
<span class="k">try</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">person</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">SingleOrDefault</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">InvalidOperationException</span> <span class="n">ex</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">persons</span> <span class="p">=</span> <span class="n">people</span><span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">x</span> <span class="p">=&gt;</span> <span class="n">x</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">)).</span><span class="nf">ToList</span><span class="p">();</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nf">TooManyPeopleFoundException</span><span class="p">(</span>
      <span class="s">$"Expected to only find one person named '</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="s">', but found </span><span class="p">{</span><span class="n">persons</span><span class="p">.</span><span class="n">Count</span><span class="p">}</span><span class="s"> in our people list: </span><span class="p">{</span><span class="kt">string</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span> <span class="n">persons</span><span class="p">)}</span><span class="s">"</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is not performant.
We’ve already mentioned that throwing exceptions is expensive, and this code now throws two.
More importantly, the <code class="language-plaintext highlighter-rouge">SingleOrDefault</code> call enumerates over the entire collection once.
In order to get the useful information to include in the error message, it has to enumerate the entire collection again using the <code class="language-plaintext highlighter-rouge">Where</code> query, so why not just do that in the first place so we only traverse the collection once?</p>

<h2 id="conclusion">Conclusion</h2>

<p>You might look at the recommended code and wonder if the extra few lines of code are worth it.
I guarantee you it is.
You can even write a helper or extension method to make the pattern easier to use.</p>

<p>Spending an extra few minutes to add detailed information to your errors will save you and your team hours of troubleshooting in the future.
There are some scenarios where it is impossible to ever solve the root issue without this extra information, such as when validating ephemeral data that no longer exists by the time you get to troubleshooting.</p>

<p>Over the past decade I have seen people advocate for <code class="language-plaintext highlighter-rouge">First</code>, <code class="language-plaintext highlighter-rouge">Single</code>, and <code class="language-plaintext highlighter-rouge">SingleOrDefault</code>.
In theory they are a good idea, but the current .NET implementation leads to more problems than it is worth.
Until the method is updated to at least allow you to provide a custom error message that includes extra information, I always caution against using them and instead recommend writing the logic yourself.</p>

<p>I even went so far as to create a checkin policy that would prevent developers from committing code that used <code class="language-plaintext highlighter-rouge">First</code>, <code class="language-plaintext highlighter-rouge">Single</code>, and <code class="language-plaintext highlighter-rouge">SingleOrDefault</code> in our flagship product.
That should give you an idea of how many developer and support staff hours were wasted tracking down “Sequence contains no matching element” and “Sequence contains more than one matching element” errors.
Similar logic could be implemented today as a Roslyn analyzer.</p>

<p>I hope you’ve found this post helpful, and that it saves future you countless hours of troubleshooting.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="DotNet" /><category term="CSharp" /><category term="DotNet" /><category term="CSharp" /><summary type="html"><![CDATA[I’ve wasted countless hours troubleshooting exceptions thrown by the .NET First, Single, and SingleOrDefault methods. In this post we’ll look at why these methods can be harmful, and what you should do instead.]]></summary></entry><entry><title type="html">Resolve PowerShell paths that do not exist</title><link href="https://blog.danskingdom.com/Resolve-PowerShell-paths-that-do-not-exist/" rel="alternate" type="text/html" title="Resolve PowerShell paths that do not exist" /><published>2023-09-28T00:00:00+00:00</published><updated>2023-10-03T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Resolve-PowerShell-paths-that-do-not-exist</id><content type="html" xml:base="https://blog.danskingdom.com/Resolve-PowerShell-paths-that-do-not-exist/"><![CDATA[<p>In this post we’ll see how to resolve a file or directory path, even if it does not exist.</p>

<h2 id="backstory">Backstory</h2>

<p>While testing my <a href="https://github.com/deadlydog/PowerShell.tiPS">tiPS PowerShell module</a> with Pester, I ran into a scenario where I wanted to mock out a function which returned a path to a configuration file.
Pester has a built-in PSDrive (<a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-psdrive">see MS docs</a>) called <code class="language-plaintext highlighter-rouge">TestDrive</code>, which is a temporary drive that is created for the duration of the test (<a href="https://pester.dev/docs/usage/testdrive">see the docs here</a>) and automatically cleaned up when the tests complete.
So rather than hardcoding a local path on disk, you can use a path like <code class="language-plaintext highlighter-rouge">TestDrive:/config.json</code>.</p>

<p>The problem was that the .NET methods, like <code class="language-plaintext highlighter-rouge">System.IO.File.WriteAllText()</code> and <code class="language-plaintext highlighter-rouge">System.IO.File.ReadAllText()</code>, do not work with the <code class="language-plaintext highlighter-rouge">TestDrive:</code> PSDrive, as they are unable to resolve the path.
The <code class="language-plaintext highlighter-rouge">Set-Content</code> and <code class="language-plaintext highlighter-rouge">Get-Content</code> cmdlets work fine with <code class="language-plaintext highlighter-rouge">TestDrive:</code>, but I wanted to use the .NET methods for performance reasons.</p>

<p>I thought an easy fix would be to just use <code class="language-plaintext highlighter-rouge">Resolve-Path</code> or <code class="language-plaintext highlighter-rouge">Join-Path -Resolve</code> to get the full path, but they return an error when the file or directory does not exist.
I did not want to manually create the file in my mock or my test, as I wanted my Pester test to ensure the module created the file properly.</p>

<h2 id="the-solution">The solution</h2>

<p>This is when I stumbled upon the <code class="language-plaintext highlighter-rouge">GetUnresolvedProviderPathFromPSPath</code> method, which can be used to resolve a file or directory path, even if it does not exist.
Here is an example of how to use it:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$configPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$ExecutionContext</span><span class="o">.</span><span class="nf">SessionState</span><span class="o">.</span><span class="nf">Path</span><span class="o">.</span><span class="nf">GetUnresolvedProviderPathFromPSPath</span><span class="p">(</span><span class="s1">'TestDrive:/config.json'</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>This resolved the path to the Windows temp directory on the C drive, and I was able to use it with the .NET <code class="language-plaintext highlighter-rouge">System.IO.File</code> methods.</p>

<p>This method works with any path, not just PSDrive paths.
For example, it also resolves this non-existent path:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">$ExecutionContext</span><span class="o">.</span><span class="nf">SessionState</span><span class="o">.</span><span class="nf">Path</span><span class="o">.</span><span class="nf">GetUnresolvedProviderPathFromPSPath</span><span class="p">(</span><span class="s2">"</span><span class="nv">$</span><span class="nn">Env</span><span class="p">:</span><span class="nv">Temp</span><span class="s2">/FileThatDoesNotExist.txt"</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>to <code class="language-plaintext highlighter-rouge">C:\Users\DAN~1.SCH\AppData\Local\Temp\FileThatDoesNotExist.txt</code>.</p>

<h2 id="a-better-solution-for-pester-psdrive-paths">A better solution for Pester PSDrive paths</h2>

<p>Later I fully read <a href="https://pester.dev/docs/usage/testdrive#working-with-net-objects">the Pester TestDrive documentation</a> and found that it actually has a built-in <code class="language-plaintext highlighter-rouge">$TestDrive</code> variable that is compatible with the .NET methods 🤦‍♂️.
So instead of using <code class="language-plaintext highlighter-rouge">TestDrive:/config.json</code>, I could just use <code class="language-plaintext highlighter-rouge">$TestDrive/config.json</code>.
I ended up changing my Pester mock to use this instead, as it is much cleaner:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$configPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$TestDrive</span><span class="s2">/config.json"</span><span class="w">
</span></code></pre></div></div>

<p>Oh well, I learned something new about PowerShell, so it was worth it.
I’m sure I’ll run into another situation down the road where <code class="language-plaintext highlighter-rouge">GetUnresolvedProviderPathFromPSPath</code> will come in handy.
Hopefully you’ve learned something new too.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="PowerShell" /><summary type="html"><![CDATA[In this post we’ll see how to resolve a file or directory path, even if it does not exist.]]></summary></entry><entry><title type="html">PowerShell class definition pros, cons, and performance comparison</title><link href="https://blog.danskingdom.com/PowerShell-class-definition-pros-cons-and-performance-comparison/" rel="alternate" type="text/html" title="PowerShell class definition pros, cons, and performance comparison" /><published>2023-09-22T00:00:00+00:00</published><updated>2023-09-25T00:00:00+00:00</updated><id>https://blog.danskingdom.com/PowerShell-class-definition-pros-cons-and-performance-comparison</id><content type="html" xml:base="https://blog.danskingdom.com/PowerShell-class-definition-pros-cons-and-performance-comparison/"><![CDATA[<p>There are 3 different approaches that can be used to define classes and enums in PowerShell.
This article will compare the pros, cons, and performance of each approach.</p>

<h2 id="the-3-approaches">The 3 approaches</h2>

<ul>
  <li><strong>PowerShell classes:</strong> Introduced in PowerShell 5.0, PowerShell has native support for classes.</li>
  <li><strong>Inline C# classes:</strong> C# classes defined in a string in PowerShell and compiled and imported at runtime.</li>
  <li><strong>Compiled assembly C# classes:</strong> C# classes defined in a .cs file, compiled to a .dll, and the .dll imported at runtime.</li>
</ul>

<h3 id="powershell-class-example">PowerShell class example</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span><span class="w"> </span><span class="nc">Person</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Name</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$Age</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><a href="https://xainey.github.io/2016/powershell-classes-and-concepts/">This blog post</a> is a great overview of PowerShell classes.</p>

<h3 id="inline-c-class-example">Inline C# class example</h3>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-Language</span><span class="w"> </span><span class="nx">CSharp</span><span class="w"> </span><span class="nt">-TypeDefinition</span><span class="w"> </span><span class="sh">@'
public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}
'@</span><span class="w">
</span></code></pre></div></div>

<p>When using this method, to make editing easier and still get syntax highlighting and intellisense, I recommend putting the C# code in a .cs file and importing it with:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$csharpCode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Path\To\Person.cs"</span><span class="w"> </span><span class="nt">-Raw</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-Language</span><span class="w"> </span><span class="nx">CSharp</span><span class="w"> </span><span class="nt">-TypeDefinition</span><span class="w"> </span><span class="nv">$csharpCode</span><span class="w">
</span></code></pre></div></div>

<h3 id="compiled-assembly-c-class-example">Compiled assembly C# class example</h3>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span> <span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Age</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The C# code is then compiled using Visual Studio / MSBuild / dotnet.exe to create a dll file, and the dll is imported in PowerShell with:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Path\To\Person.dll"</span><span class="w">
</span></code></pre></div></div>

<h2 id="performance-comparison">Performance comparison</h2>

<p>I created <a href="https://github.com/deadlydog/PowerShell.Experiment.ClassPerformanceComparison">this PowerShell.Experiment.ClassPerformanceComparison repository</a> to test and compare the 3 different ways to define and import classes and enums.
The test defines identical classes that are imported using each method, and the time required to import each class is measured.
It defines a very basic class, and a slightly larger and more complex class, and duplicates each class 3 times just with a different name, so there are 6 classes in total that are imported.
For more details, see the repo and the code.</p>

<p>The results of the test are below:</p>

<table>
  <thead>
    <tr>
      <th>Class</th>
      <th>PowerShell Classes</th>
      <th>Inline C# Classes</th>
      <th>Compiled Assembly C# Classes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Basic1</td>
      <td>89ms</td>
      <td>764ms</td>
      <td>10ms</td>
    </tr>
    <tr>
      <td>Basic2</td>
      <td>10ms</td>
      <td>25ms</td>
      <td>9ms</td>
    </tr>
    <tr>
      <td>Basic3</td>
      <td>9ms</td>
      <td>36ms</td>
      <td>8ms</td>
    </tr>
    <tr>
      <td>Large1</td>
      <td>13ms</td>
      <td>230ms</td>
      <td>8ms</td>
    </tr>
    <tr>
      <td>Large2</td>
      <td>11ms</td>
      <td>55ms</td>
      <td>8ms</td>
    </tr>
    <tr>
      <td>Large3</td>
      <td>12ms</td>
      <td>28ms</td>
      <td>8ms</td>
    </tr>
  </tbody>
</table>

<h3 id="powershell-class-results">PowerShell class results</h3>

<p>The first PowerShell class declared is a little slow to import, but subsequent declarations are much faster.
I suspect this is due to PowerShell loading some assemblies into memory that it needs to import a PowerShell class/enum.
Once those assemblies are loaded, subsequent imports are much faster.</p>

<p>It appears that the size of the class may have a bit of impact on the import time, but it is not significant.</p>

<h3 id="inline-c-class-results">Inline C# class results</h3>

<p>Inline C# classes are very slow to import.
I suspect this is because the C# code is compiled at runtime before being loaded into memory.
The first import is especially slow.
Also, any dependencies that the class references need to be loaded into memory.
The Large class references some types not referenced by the Basic class, so I think that is why the initial Large class import is slow even after the Basic class has already been loaded.</p>

<h3 id="compiled-assembly-c-class-results">Compiled assembly C# class results</h3>

<p>Compiled assembly C# classes are the fastest to import every time, since the code has been pre-compiled.</p>

<h2 id="other-considerations">Other considerations</h2>

<p>In <a href="https://blog.danskingdom.com/How-and-where-to-properly-define-classes-and-enums-in-your-PowerShell-modules/">this post</a> I looked at the usability implications of using PowerShell classes vs Inline C# classes.
You can read the post for more details, but the takeaways are:</p>

<ul>
  <li>If creating a module with PowerShell classes, you <em>must</em> define them directly in the .psm1 file; they cannot be defined in another file and dot-sourced into the .psm1 file.
This prevents organizing your class code into multiple files.</li>
  <li>If creating a module with PowerShell classes, consumers of the module must use <code class="language-plaintext highlighter-rouge">using module YourModule</code> instead of <code class="language-plaintext highlighter-rouge">Import-Module YourModule</code> to be able to reference to the class and enum types, otherwise PowerShell will give an error that it cannot find the type.</li>
</ul>

<p>C# classes and enums, whether inline or compiled, do not have these limitations.</p>

<p>However, using C# code in PowerShell does have its own drawbacks:</p>

<ul>
  <li>You are not able to step into C# code from PowerShell to debug it.</li>
  <li>You can not write to the Debug, Verbose, or Warning streams from C#.</li>
</ul>

<h3 id="additional-powershell-class-considerations">Additional PowerShell class considerations</h3>

<ul>
  <li>In addition to the module limitations mentioned above, PowerShell classes were introduced in PowerShell 5.0, so we can’t use them if we want to support PowerShell 3.0 and 4.0.</li>
</ul>

<h3 id="additional-inline-c-class-considerations">Additional Inline C# class considerations</h3>

<ul>
  <li><a href="https://stackoverflow.com/a/40789694/602585">Windows PowerShell only supports C# 5.0</a>, so we can’t use any newer language features.</li>
</ul>

<h3 id="additional-compiled-assembly-c-class-considerations">Additional Compiled assembly C# class considerations</h3>

<ul>
  <li>You must compile the C# classes into an assembly, which is an extra development step.</li>
  <li>The assembly must be compiled against .NET Standard 2.0 so that it can be used in both Windows PowerShell and PowerShell Core.
This means <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version">it supports C# v7.3</a>, but not newer features.</li>
</ul>

<p>See <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history">the list of C# versions and their features</a> to know which C# features you can use.</p>

<h2 id="my-recommendation">My recommendation</h2>

<p>If you are not creating a module, but instead just writing a script, I recommend using PowerShell classes.
It avoids context switching between languages and is still very fast to run.</p>

<p>If you are creating a module and don’t intend for consumers of the module to use the classes and enums; that is, they will only be referenced internally by your module, then using PowerShell classes may still be fine.</p>

<p>If you are creating a module and intend to expose your classes and enums for consumers to use, then I recommend using C# classes and enums.</p>

<p>If the module load time is not a concern, then inline C# classes may be fine.
If you add many inline C# classes though, it could take several seconds to load the module.</p>

<p>If module load time is a concern, then I recommend using compiled assembly C# classes and enums.
Using a compiled assembly gives you the benefit of compiler checking and being able to use other development tools if you like.
However, it also adds additional development complexity as you need to compile the assembly after any code changes and before you can use it in PowerShell.
This is the approach I took in my <a href="https://github.com/deadlydog/PowerShell.tiPS">tiPS PowerShell module</a>, as it is intended to be added to a user’s PowerShell profile and would be loaded every session, so you can look at that module for an example.</p>

<p>Since you are not able to debug C# classes when running PowerShell, nor write to all of the output streams, I recommend keeping your classes very simple and using them mostly as a data structure.
They should mostly just be properties with no or very few methods.
If complex operations need to be performed on the class data, create PowerShell functions that accept the class as a parameter and perform the operation.
This will allow you to step through and debug the complex code, and write to all of the output streams if needed.</p>

<h2 id="create-your-module-in-c-instead-of-powershell">Create your module in C# instead of PowerShell</h2>

<p>It is possible to create modules and cmdlets entirely in C# instead of PowerShell.
This gives you all of the benefits that come with writing C#: strongly typed code, compiler checking, great development tools, etc.
For more information on how to do this, <a href="https://blog.danskingdom.com/Create-and-test-PowerShell-Core-cmdlets-in-CSharp/">check out my other blog post</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>In this article we’ve seen the pros, cons, and performance of the 3 different ways to define and import classes and enums in PowerShell.
I hope this helps you decide which approach is best for your scenario.</p>

<p>If you have any questions or comments, please leave them below.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Performance" /><category term="PowerShell" /><category term="Performance" /><summary type="html"><![CDATA[There are 3 different approaches that can be used to define classes and enums in PowerShell. This article will compare the pros, cons, and performance of each approach.]]></summary></entry><entry><title type="html">Get PowerShell tips automatically in your terminal with the tiPS module</title><link href="https://blog.danskingdom.com/Get-PowerShell-tips-automatically-in-your-terminal-with-the-tiPS-module/" rel="alternate" type="text/html" title="Get PowerShell tips automatically in your terminal with the tiPS module" /><published>2023-09-15T00:00:00+00:00</published><updated>2023-09-15T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Get-PowerShell-tips-automatically-in-your-terminal-with-the-tiPS-module</id><content type="html" xml:base="https://blog.danskingdom.com/Get-PowerShell-tips-automatically-in-your-terminal-with-the-tiPS-module/"><![CDATA[<p>There are a lot of great PowerShell tips out there, but we often don’t proactively look for them.
It’s hard to know what you don’t know, and easy to miss out on some great tips that could make your life easier.
The <a href="https://www.powershellgallery.com/packages/tiPS">tiPS module</a> can be used to display a PowerShell tip on demand, or automatically when you open a new PowerShell session, helping to broaden your PowerShell knowledge and stay up-to-date.</p>

<h2 id="installation">Installation</h2>

<p>Install and configure the tiPS module by running the following commands in a PowerShell terminal:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">tiPS</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">CurrentUser</span><span class="w">
</span><span class="n">Add-TiPSImportToPowerShellProfile</span><span class="w">
</span><span class="nx">Set-TiPSConfiguration</span><span class="w"> </span><span class="nt">-AutomaticallyWritePowerShellTip</span><span class="w"> </span><span class="nx">Daily</span><span class="w"> </span><span class="nt">-AutomaticallyUpdateModule</span><span class="w"> </span><span class="nx">Weekly</span><span class="w">
</span></code></pre></div></div>

<h2 id="display-a-tip">Display a tip</h2>

<p>To display a PowerShell tip, simply run the <code class="language-plaintext highlighter-rouge">Write-PowerShellTip</code> command, or use its alias <code class="language-plaintext highlighter-rouge">tips</code>.</p>

<h2 id="demo">Demo</h2>

<p>Here’s a quick demo of installing tiPS and getting tips on demand, and then configuring tiPS to automatically display a tip every time you open a new PowerShell session:</p>

<p><img src="/assets/Posts/2023-09-15-Get-PowerShell-tips-automatically-in-your-terminal-with-the-tiPS-module/InstallAndConfigureTiPSModule.gif" alt="tiPS demo" /></p>

<p>While the demo shows displaying a new tip on every PowerShell session, I recommend configuring it to show a new tip daily so that you don’t get too distracted by tips while doing your day-to-day work.</p>

<h2 id="more-info">More info</h2>

<p>tiPS is cross-platform, so it works on Windows, macOS, and Linux.
It supports both Windows PowerShell (e.g. v5.1) and PowerShell Core (e.g. v7+).</p>

<p>tiPS is open source and intended to be community driven.
If you have a PowerShell tip, module, blog post, or community event that you think others would find useful, submit a pull request to have it added.</p>

<p>Checkout <a href="https://github.com/deadlydog/PowerShell.tiPS">the tiPS GitHub repo</a> for more information.</p>

<p>Happy scripting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Learning" /><category term="PowerShell" /><category term="Learning" /><summary type="html"><![CDATA[There are a lot of great PowerShell tips out there, but we often don’t proactively look for them. It’s hard to know what you don’t know, and easy to miss out on some great tips that could make your life easier. The tiPS module can be used to display a PowerShell tip on demand, or automatically when you open a new PowerShell session, helping to broaden your PowerShell knowledge and stay up-to-date.]]></summary></entry><entry><title type="html">Easily profile your PowerShell code with the Profiler module</title><link href="https://blog.danskingdom.com/Easily-profile-your-PowerShell-code-with-the-Profiler-module/" rel="alternate" type="text/html" title="Easily profile your PowerShell code with the Profiler module" /><published>2023-09-09T00:00:00+00:00</published><updated>2023-09-17T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Easily-profile-your-PowerShell-code-with-the-Profiler-module</id><content type="html" xml:base="https://blog.danskingdom.com/Easily-profile-your-PowerShell-code-with-the-Profiler-module/"><![CDATA[<p>Premature optimization may be the root of all evil, but that doesn’t mean you shouldn’t optimize your code where it matters.
Rather than guessing which code is slow and spending time optimizing code that doesn’t need it, you should profile your code to find the true bottlenecks.
At that point you can decide if the code is slow enough to warrant optimizing.</p>

<p>Jakub Jareš has created the amazing <a href="https://github.com/nohwnd/Profiler">Profiler module GitHub repo</a> for profiling PowerShell code to find which parts are the slowest.
Installing and using it is a breeze.</p>

<p>In this post I will walk through how I used Profiler to find a surprisingly slow part of a new PowerShell module I am developing, called <a href="https://github.com/deadlydog/PowerShell.tiPS">tiPS</a>.
I noticed that importing tiPS in my PowerShell profile was noticeably slowing down my PowerShell session startup time, so I decided to profile it to see if I could optimize it.</p>

<h2 id="tldr">TL;DR</h2>

<p>If you don’t want to read this entire post, just run these commands and thank me (and Jakub) later:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Profiler</span><span class="w">
</span><span class="nv">$trace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Trace-Script</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="s1">'C:\Path\To\Script.ps1'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$trace</span><span class="o">.</span><span class="nf">Top50SelfDuration</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-GridView</span><span class="w">
</span></code></pre></div></div>

<h2 id="installing-the-profiler-module">Installing the Profiler module</h2>

<p>The Profiler module is available on <a href="https://www.powershellgallery.com/packages/Profiler/">the PowerShell Gallery here</a>, so you can install it with:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Profiler</span><span class="w">
</span></code></pre></div></div>

<h2 id="tracing-code-with-the-profiler-module">Tracing code with the Profiler module</h2>

<p>You profile code by tracing it, which is done with the <code class="language-plaintext highlighter-rouge">Trace-Script</code> command.
This works similarly to the <code class="language-plaintext highlighter-rouge">Measure-Command</code> cmdlet, but provides more detailed information.</p>

<p>To use the Profiler, simply wrap the code you want to profile in a script block and pass it to the <code class="language-plaintext highlighter-rouge">Trace-Script</code> command, and capture the output in a variable.</p>

<p>Trace some code inline:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$trace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Trace-Script</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="c"># Code to profile goes here</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Trace some code in a script file:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$trace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Trace-Script</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="s1">'C:\Path\To\Script.ps1'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Trace some code in a script file that takes parameters:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$trace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Trace-Script</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="s1">'C:\Path\To\Script.ps1'</span><span class="w"> </span><span class="nt">-Parameter</span><span class="w"> </span><span class="s1">'Value'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Trace the code in your <code class="language-plaintext highlighter-rouge">$Profile</code> script that is run on every new session:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pwsh</span><span class="w"> </span><span class="nt">-NoProfile</span><span class="w"> </span><span class="nt">-NoExit</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$trace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Trace-Script</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="nv">$Profile</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Note: Depending on how you have your profile configured (<a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles">MS docs</a>), you may need to reference one of the other profile file paths, such as <code class="language-plaintext highlighter-rouge">$Profile.CurrentUserAllHosts</code>.</p>

<p>Let’s walk through tracing the code I have in my profile script to see if I can reduce my PowerShell session startup time.</p>

<p>You will notice some summary information is output to the console when you run the <code class="language-plaintext highlighter-rouge">Trace-Script</code> command, including the total <code class="language-plaintext highlighter-rouge">Duration</code>, which is the same info that the <code class="language-plaintext highlighter-rouge">Measure-Command</code> cmdlet would have given us.</p>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-script-summary-output-screenshot.png" alt="Trace-Script summary output" /></p>

<h2 id="viewing-the-profile-trace-information">Viewing the profile trace information</h2>

<p>Now that we have the trace information in the <code class="language-plaintext highlighter-rouge">$trace</code> variable, you can inspect its properties to view the trace info.</p>

<p>The <code class="language-plaintext highlighter-rouge">TotalDuration</code> property is the same TimeSpan that <code class="language-plaintext highlighter-rouge">Measure-Command</code> would have given us:</p>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-total-duration-screenshot.png" alt="Trace TotalDuration property output" /></p>

<p>The real value of the Profiler is in the <code class="language-plaintext highlighter-rouge">Top50SelfDuration</code> and <code class="language-plaintext highlighter-rouge">Top50Duration</code> properties, which is a list of the top 50 slowest commands in the trace.
The 2 properties show similar information, just sorted based on <code class="language-plaintext highlighter-rouge">SelfDuration</code> or <code class="language-plaintext highlighter-rouge">Duration</code> respectively.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">SelfDuration</code> is the time spent in the command itself, not including any time spent in functions that it called.
This shows you the lines of code that are actually slow in the script.</li>
  <li><code class="language-plaintext highlighter-rouge">Duration</code> is the time spent in the command, including all other functions it called.
It shows the bigger pieces that are slow because they contain smaller slow pieces.</li>
</ul>

<p>A good strategy is to look at the <code class="language-plaintext highlighter-rouge">Top50SelfDuration</code> first to see which specific lines of code are the slowest.
You can then look at the <code class="language-plaintext highlighter-rouge">Top50Duration</code> to see which larger parts of your script take the most time, and see if it matches your expectations.</p>

<p>Here is the output of the <code class="language-plaintext highlighter-rouge">Top50SelfDuration</code> property:</p>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-top-50-self-duration-table.png" alt="Trace Top50SelfDuration property output" /></p>

<p>In this screenshot we can see that the top offender (the first row) is a line in the <code class="language-plaintext highlighter-rouge">tiPS</code> module’s <code class="language-plaintext highlighter-rouge">Configuration.ps1</code> file on line <code class="language-plaintext highlighter-rouge">8</code>, which is called once and taking 693 milliseconds to execute.
<code class="language-plaintext highlighter-rouge">tiPS</code> is a module that I am currently developing and have added to my profile to automatically import it on every new session.</p>

<p>By default the <code class="language-plaintext highlighter-rouge">Top50SelfDuration</code> and <code class="language-plaintext highlighter-rouge">Top50Duration</code> display their output in a table view.
Unfortunately the number of columns displayed is based on your console window width.
If we make the console window wider, we can see 2 more very useful columns: <code class="language-plaintext highlighter-rouge">Function</code> and <code class="language-plaintext highlighter-rouge">Text</code>, which is the actual line of code that was executed.</p>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-top-50-self-duration-table-wider-width.png" alt="Trace Top50SelfDuration property output with wider console window" /></p>

<p>Even with the wider console window, much of the text is still cut off.
To see the full text we have a couple options.
We can pipe the output to <code class="language-plaintext highlighter-rouge">Format-List</code>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$trace</span><span class="o">.</span><span class="nf">Top50SelfDuration</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-List</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-top-50-self-duration-list.png" alt="Trace Top50SelfDuration property output in Format-List" /></p>

<p>This is good for seeing details about each individual entry, but not great for viewing the entire list.</p>

<p>My preferred approach is to pipe the results to <code class="language-plaintext highlighter-rouge">Out-GridView</code>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$trace</span><span class="o">.</span><span class="nf">Top50SelfDuration</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-GridView</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-top-50-self-duration-grid-view.png" alt="Trace Top50SelfDuration property output in Out-GridView" /></p>

<p>This allows me to see all of the columns, reorder and hide columns I don’t care about, as well as sort and filter the results.</p>

<p>Now that we have the <code class="language-plaintext highlighter-rouge">Text</code> column, we can see that the top offender is a call to <code class="language-plaintext highlighter-rouge">Add-Type -Language CSharp</code>.
Since <code class="language-plaintext highlighter-rouge">tiPS</code> is my module, I know that line is being used to compile and import some inline C# code.
The 3rd row shows the same operation for importing a different C# file.
Since it is a single line of code calling a library that I do not own, there’s not much I can do to optimize it outside of considering a different strategy for importing C# code or not using C# at all, which may be something to consider.</p>

<p>Moving on, the 2nd row shows a call to <code class="language-plaintext highlighter-rouge">Set-PoshPrompt</code> taking 624 milliseconds.
I use this third party module to customize my prompt with oh-my-posh.
In fact, looking at the <code class="language-plaintext highlighter-rouge">Module</code> column, you can see many of the slowest commands are coming from 3 third party modules that I import in my profile script: <code class="language-plaintext highlighter-rouge">oh-my-posh</code>, <code class="language-plaintext highlighter-rouge">posh-git</code>, and <code class="language-plaintext highlighter-rouge">Terminal-Icons</code>.
Since it is third party code there’s not much I can do outside of not loading those modules automatically at startup, which I may decide to do if they impact my startup time enough.</p>

<p>With that in mind, let’s focus on the code that I can optimize.
The 6th row shows another top offending line in the <code class="language-plaintext highlighter-rouge">tiPS</code> module.</p>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-tips-dot-source-offending-line.png" alt="Trace Top50SelfDuration property output in Out-GridView with 6th row highlighted" /></p>

<p>Notice that this line has a total <code class="language-plaintext highlighter-rouge">Duration</code> of 1054 milliseconds, due to it being called 15 times.
On initial inspection, it appears that this line of code is slowing the module load time even more than our initial top offender (we’ll come back to this).
This is made more apparent when we sort by <code class="language-plaintext highlighter-rouge">Duration</code>:</p>

<p><img src="/assets/Posts/2023-09-09-Easily-profile-your-PowerShell-code-with-the-Profiler-module/trace-tips-dot-source-offending-line-sorted-by-duration.png" alt="Trace Top50Duration property output in Out-GridView sorted by Duration" /></p>

<p>We can ignore the top row as it is dot-sourcing the file that contains all of my custom profile code, which is why the <code class="language-plaintext highlighter-rouge">Percent</code> shows as <code class="language-plaintext highlighter-rouge">99.79</code>.
I keep my custom profile code in a separate file and dot-source it into the official $Profile so that it can be shared between PowerShell 5 and Pwsh 7, as well as automatically backed up on OneDrive.</p>

<p>Looking in my <code class="language-plaintext highlighter-rouge">tiPS.psm1</code> file on line <code class="language-plaintext highlighter-rouge">25</code>, I see that it is looping over all of the files in the module directory and dot-sourcing them:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$functionFilePathsToImport</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="o">.</span><span class="w"> </span><span class="bp">$_</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This dot-sourcing strategy allows for better code organization by keeping module functions in separate files, rather than having one giant .psm1 file with all of the module code.
A quick Google search shows me that others have come across <a href="https://becomelotr.wordpress.com/2017/02/13/expensive-dot-sourcing/">this dot-sourcing</a> <a href="https://superuser.com/questions/1170619/is-dot-sourcing-slower-than-just-reading-file-content">performance issue</a> <a href="https://www.codykonior.com/2019/04/18/expensive-dot-sourcing-in-powershell-modules-revisited/">as well</a>.
Naively we may think that this is giving additional evidence that the dot-sourcing may be the main issue.</p>

<p>Earlier we saw that the <code class="language-plaintext highlighter-rouge">Add-Type</code> command was taking 693ms in Configuration.ps1, and 210ms in PowerShellTip.ps1.
That is 903ms total.
I did a <code class="language-plaintext highlighter-rouge">Trace-Script -ScriptBlock { Import-Module -Name tiPS }</code> to see how long it takes to just import the tiPS module, and found the entire import takes 1180ms.
Configuration.ps1 and PowerShellTip.ps1 are 2 of the 15 files that get dot-sourced in the module, and they alone take 903ms of the 1180ms to load the module.</p>

<p>This highlights the difference between <code class="language-plaintext highlighter-rouge">SelfDuration</code> and <code class="language-plaintext highlighter-rouge">Duration</code>.
The <code class="language-plaintext highlighter-rouge">Duration</code> of the dot-sourcing was including the time spent in all of the operations of the dot-sourced files, such as the <code class="language-plaintext highlighter-rouge">Add-Type</code>operations.
In the screenshot we can see that the dot-sourcing <code class="language-plaintext highlighter-rouge">Duration</code> is 1054ms, and the <code class="language-plaintext highlighter-rouge">SelfDuration</code> is 129ms.
So the act of dot-sourcing the 15 files, regardless of what the files contain, is taking 129ms.
We know it takes 903ms to execute the two <code class="language-plaintext highlighter-rouge">Add-Type</code> commands in the dot-sourced files, so that means the time it takes to execute the operations in the other 13 files being dot-sourced is 1054ms - 903ms = 151ms.</p>

<p>So dot-sourcing 15 files takes 129ms, which isn’t too bad.
I imagine (but did not test) the time will increase linearly with the number of files dot-sourced, so if you have a large project that dot-sources hundreds of files, you could be looking at several seconds of time spent just dot-sourcing the files; not executing the code in the files.
While dot-sourcing is a bit slow, it is not the main offender in my scenario.
The main performance issue is the <code class="language-plaintext highlighter-rouge">Add-Type</code> commands to compile and import the C# code.
I never anticipated that one command would have such a drastic impact on the module load time!</p>

<p>Since the tiPS module is intended to be loaded on every new PowerShell session, I need to find a way to speed up the module load time.
This might mean not using C# code, or taking some other approach; I’m not sure yet.
The main point is that I now know where the bottleneck is and can focus my efforts on optimizing that code.</p>

<h2 id="conclusion">Conclusion</h2>

<p>By using the Profiler module for less than 2 minutes I was able to identify a major performance issue that I would not have otherwise known about.
Without it, I likely would have spent time optimizing other parts of my code that would not have as much impact.</p>

<p>Profiling your code is as easy as:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Profiler</span><span class="w">
</span></code></pre></div></div>

<p>then:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$trace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Trace-Script</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="s1">'C:\Path\To\Script.ps1'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$trace</span><span class="o">.</span><span class="nf">Top50SelfDuration</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-GridView</span><span class="w">
</span></code></pre></div></div>

<p>Profiler has more features that I did not cover in this post, such as using Flags to easily change the functionality of the code in the script block and compare them (e.g. to compare two different implementations), and functions for running the script block multiple times and taking the average performance metrics.
Be sure to check out <a href="https://github.com/nohwnd/Profiler">the Profiler git repo</a> for more info.</p>

<p>I hope you’ve found this post helpful.</p>

<p>Happy profiling!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Performance" /><category term="PowerShell" /><category term="Performance" /><summary type="html"><![CDATA[Premature optimization may be the root of all evil, but that doesn’t mean you shouldn’t optimize your code where it matters. Rather than guessing which code is slow and spending time optimizing code that doesn’t need it, you should profile your code to find the true bottlenecks. At that point you can decide if the code is slow enough to warrant optimizing.]]></summary></entry><entry><title type="html">Automatically create new sessions to improve PowerShell development with classes in VS Code</title><link href="https://blog.danskingdom.com/Automatically-create-new-sessions-to-improve-PowerShell-development-with-classes-in-VS-Code/" rel="alternate" type="text/html" title="Automatically create new sessions to improve PowerShell development with classes in VS Code" /><published>2023-08-18T00:00:00+00:00</published><updated>2023-08-18T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Automatically-create-new-PowerShell-sessions-to-improve-working-with-classes-in-VS-Code</id><content type="html" xml:base="https://blog.danskingdom.com/Automatically-create-new-sessions-to-improve-PowerShell-development-with-classes-in-VS-Code/"><![CDATA[<p>Developing PowerShell scripts and modules that reference classes and binary modules can be a bit painful, as in order to load a new version of the class or module you need to restart the PowerShell session to unload the old version.
Having to manually restart PowerShell every time you make a change to a class or binary module gets old pretty fast.
Luckily, <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell">the Visual Studio Code PowerShell extension</a> has a feature that can help with this.</p>

<p>Once you’ve installed the PowerShell extension in VS Code, you can enable the <code class="language-plaintext highlighter-rouge">PowerShell › Debugging: Create Temporary Integrated Console</code> setting.</p>

<p><img src="/assets/Posts/2023-08-18-Automatically-create-new-PowerShell-sessions-to-improve-working-with-classes-in-VS-Code/vs-code-setting-to-enable-powershell-temporary-integrated-console.png" alt="Screenshot of the Visual Studio Code setting to enable PowerShell temporary integrated console" /></p>

<p>Now every time you debug a PowerShell script, it will first create a new temporary PowerShell terminal and then run the script in that terminal, ensuring no previous variables are still lingering in memory and that the latest version of your classes and binary modules are loaded.
No more manually restarting your PowerShell terminals when developing classes or binary modules!</p>

<p>I hope you find this useful.
For more Visual Studio Code settings you might want to tweak, <a href="https://blog.danskingdom.com/Visual-Studio-Code-default-settings-to-change/">check out this post</a>.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Visual Studio Code" /><category term="PowerShell" /><category term="Visual Studio Code" /><summary type="html"><![CDATA[Developing PowerShell scripts and modules that reference classes and binary modules can be a bit painful, as in order to load a new version of the class or module you need to restart the PowerShell session to unload the old version. Having to manually restart PowerShell every time you make a change to a class or binary module gets old pretty fast. Luckily, the Visual Studio Code PowerShell extension has a feature that can help with this.]]></summary></entry><entry><title type="html">How and where to properly define classes and enums in your PowerShell modules</title><link href="https://blog.danskingdom.com/How-and-where-to-properly-define-classes-and-enums-in-your-PowerShell-modules/" rel="alternate" type="text/html" title="How and where to properly define classes and enums in your PowerShell modules" /><published>2023-08-17T00:00:00+00:00</published><updated>2023-09-22T00:00:00+00:00</updated><id>https://blog.danskingdom.com/How-and-where-to-properly-define-classes-and-enums-in-your-PowerShell-modules</id><content type="html" xml:base="https://blog.danskingdom.com/How-and-where-to-properly-define-classes-and-enums-in-your-PowerShell-modules/"><![CDATA[<p>I recently created a PowerShell script module that defines classes and enums.
Everything worked fine locally, but broke when I tested the module on a build server.
This was odd, as the module did not have any dependencies.</p>

<p>In this post I’ll explain what I did, how to properly define PowerShell classes and enums in your PowerShell script modules to avoid issues for yourself and consumers of your modules, and why you may want to consider using C# classes and enums instead.</p>

<h2 id="tldr-just-tell-me-the-proper-way-to-do-it">TL;DR: Just tell me the proper way to do it</h2>

<p>With the current latest PowerShell version, v7.3.6, there are still nuances to using PowerShell native classes and enums in modules.
The easiest way to avoid the issues they pose is to simply not use them.
Instead, use C# classes and enums in your PowerShell modules.</p>

<p>C# classes and enums:</p>

<ul>
  <li>Can be defined in their own files and dot-sourced into the psm1 file.</li>
  <li>Allow module consumers to use <code class="language-plaintext highlighter-rouge">Import-Module</code> and still have full access to the class/enum types.</li>
</ul>

<p>If you decide to still use PowerShell native classes and enums:</p>

<ol>
  <li>Define your classes and enums directly in the <code class="language-plaintext highlighter-rouge">.psm1</code> file.
Do <em>NOT</em> define them in a separate file and include them in the psm1 file via dot-sourcing or any other method.</li>
  <li>When importing a module that uses classes or enums into your scripts, use the <code class="language-plaintext highlighter-rouge">using module</code> command (<a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_using#module-syntax">MS docs</a>), not <code class="language-plaintext highlighter-rouge">Import-Module</code>.</li>
</ol>

<p>If you do not follow these 2 rules with PowerShell native classes/enums, then you may run into build or runtime errors like:</p>

<blockquote>
  <p>Unable to find type [MyClass/MyEnum]</p>
</blockquote>

<p>I also discuss the pros and cons of the different ways to define classes and enums in PowerShell <a href="https://blog.danskingdom.com/PowerShell-class-definition-pros-cons-and-performance-comparison/">in this post</a>.</p>

<h2 id="what-is-a-powershell-native-class-and-enum">What is a PowerShell native class and enum?</h2>

<p>Let’s ensure we are on the same page with regard to PowerShell native <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes">classes</a> and <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_enum">enums</a>.
PowerShell native classes and enums were introduced in PowerShell 5, and are a way to define strongly-typed objects in PowerShell.</p>

<p>Here is an example of a basic PowerShell class definition:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span><span class="w"> </span><span class="nc">MyClass</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Name</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$Age</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>And an example of a PowerShell enum definition:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">enum</span><span class="w"> </span><span class="n">MyEnum</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Value1</span><span class="w">
    </span><span class="nx">Value2</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>See <a href="https://xainey.github.io/2016/powershell-classes-and-concepts/">this amazing blog post on PowerShell classes</a> for more information about how to use them.
These is also <a href="https://stephanevg.github.io/powershell/class/module/DATA-How-To-Write-powershell-Modules-with-classes/">this blog post</a> that goes over some of the pros and cons of using classes.</p>

<p>I mostly use classes for passing around strongly-typed data objects (rather than a <code class="language-plaintext highlighter-rouge">hashtable</code> or <code class="language-plaintext highlighter-rouge">PSCustomObject</code>), so I can guarantee consistency between all of the objects.</p>

<p>I love using enums where possible for properties with a limited set of specific values, as it makes the code more readable and less error-prone, and PowerShell offers autocompletion when using them.</p>

<h2 id="backstory-how-i-defined-classes-in-my-module-and-encountered-problems">Backstory: How I defined classes in my module and encountered problems</h2>

<p>Rather than defining all of my functions directly in the module’s <code class="language-plaintext highlighter-rouge">psm1</code> file and ending up with 2000+ line file, I thought I would follow a common code organization convention and define each of my functions in a separate file, as mentioned in <a href="https://tomasdeceuninck.github.io/2018/04/17/PowerShellModuleStructure.html">this blog post</a>.
This has a number of benefits, such as making it easier to find the code you are looking for and reducing merge conflicts.</p>

<p>Similarly, I decided to put each of my <code class="language-plaintext highlighter-rouge">class</code> and <code class="language-plaintext highlighter-rouge">enum</code> definitions in their own files.
<a href="https://github.com/MSAdministrator/TemplatePowerShellModule">This open-source PowerShell module template</a> follows the convention of putting each function and type in their own file, and <a href="https://github.com/MSAdministrator/TemplatePowerShellModule/blob/8e5510fc2f172c59f06fa91d4c4a754fd521ed0a/TemplatePowerShellModule/ModuleName.psm1#L1">showed how to include class files</a> with the <code class="language-plaintext highlighter-rouge">using module</code> command.
So following that example, I put my classes and enums in their own files and included them in my module’s <code class="language-plaintext highlighter-rouge">psm1</code> file with the <code class="language-plaintext highlighter-rouge">using module</code> command.
I created some Pester tests and verified that everything worked properly.</p>

<p>My next step was to setup a CI/CD pipeline to automatically build and publish my module to the PowerShell Gallery.
I created a GitHub Action workflow to run all of the Pester tests before packaging up the module.
Strangely, the tests failed with the following error:</p>

<blockquote>
  <p>Unable to find type [MyClass/MyEnum]</p>
</blockquote>

<h2 id="experimenting-and-reaching-out-for-help">Experimenting and reaching out for help</h2>

<p>It was very strange that the Pester tests passed on my local machine, but failed on the build server.
I created <a href="https://github.com/deadlydog/PowerShell.Experiment.ClassInModule">this small sample repo</a> and reproduced the issue.
From there, I created <a href="https://stackoverflow.com/questions/76886628/powershell-module-with-class-defined-in-separate-file-fails-pester-tests-in-gith">this Stack Overflow question</a> and reached out <a href="https://twitter.com/deadlydog/status/1690106592182591490?s=20">on Twitter (X) here</a> and <a href="https://hachyderm.io/@deadlydog/110873007402908403">Mastodon here</a> for help.
The Mastodon PowerShell community is strong and offered some great suggestions and explanations.</p>

<h2 id="experiment-results">Experiment results</h2>

<p>I kept experimenting with <a href="https://github.com/deadlydog/PowerShell.Experiment.ClassInModule">my sample repo</a>, and created a Dev Container to try and eliminate any anomalies that may be due to my local machine.
The tests are explained in more detail in the repo’s ReadMe, and you can virw the source code of the tests performed.</p>

<p>PowerShell version 7.2.13 was used to produce these results.
To ensure my local machine was not impacting the results, all results shown below are from running the tests in GitHub Actions, on both Windows and Linux agents.</p>

<h3 id="referencing-the-powershell-classenum-in-the-module">Referencing the PowerShell class/enum in the module</h3>

<p>To include a class/enum that I created within the module, I tried 3 different methods:</p>

<ol>
  <li>With “using module” in the psm1 file: <code class="language-plaintext highlighter-rouge">using module .\Classes\MyClass.psm1</code>
    <ul>
      <li>This test is done without using Export-ModuleMember like a real separate module would; it just has the class/enum definition in it.</li>
    </ul>
  </li>
  <li>With dot-sourcing in the psm1 file: <code class="language-plaintext highlighter-rouge">. "$PSScriptRoot\Classes\MyClass.ps1</code></li>
  <li>Defining the class/enum directly in the psm1 file, instead of in its own file.</li>
</ol>

<p>The results of using the different methods to reference a PowerShell native class/enum in the script module are as follows:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Class/Enum can be used by module functions</th>
      <th>Class/Enum type can be used outside of module</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Class/Enum file included with <code class="language-plaintext highlighter-rouge">using module</code></td>
      <td>❌</td>
      <td>❌</td>
    </tr>
    <tr>
      <td>Class/Enum file included with dot-sourcing</td>
      <td>✔️</td>
      <td>❌</td>
    </tr>
    <tr>
      <td>Class/Enum defined in the psm1 file</td>
      <td>✔️</td>
      <td>✔️</td>
    </tr>
  </tbody>
</table>

<p>If I use <code class="language-plaintext highlighter-rouge">using module</code> to include the file with the class defined in it, then the class cannot be used by the module functions, and the class type cannot be used outside of the module.
Simply put, it does not work at all (strangely though it worked when testing on my local machine 🤷‍♂️, so it seems unreliable at best).</p>

<p>If I dot-source the file with the class, then the class can be used by the module functions, but the class type cannot be referenced outside of the module.
Anytime the class name is referenced you get the <code class="language-plaintext highlighter-rouge">Unable to find type</code> error.</p>

<p>If I define the class in the psm1 file, then the class can be used by the module functions, and the class type can be used outside of the module.</p>

<p>Enums behaved the same as classes in all of the tests that were performed.</p>

<h3 id="referencing-the-module-from-a-script">Referencing the module from a script</h3>

<p>I also tested the 2 different ways a module can be imported into a script; with <code class="language-plaintext highlighter-rouge">Import-Module</code> and <code class="language-plaintext highlighter-rouge">using module</code>.
An important distinction between the two is that <code class="language-plaintext highlighter-rouge">Import-Module</code> is a cmdlet, while <code class="language-plaintext highlighter-rouge">using module</code> is a language keyword, like <code class="language-plaintext highlighter-rouge">if</code> or <code class="language-plaintext highlighter-rouge">foreach</code>.
The two are fundamentally different, and behave differently when importing modules.</p>

<p>The results below assume the class/enum is referenced directly in the psm1 file for script modules, as that is the recommended approach to take after seeing the results from the previous section.</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Class/Enum can be used by module functions</th>
      <th>Class/Enum type can be used outside of module</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Module imported with <code class="language-plaintext highlighter-rouge">Import-Module</code></td>
      <td>✔️</td>
      <td>❌</td>
    </tr>
    <tr>
      <td>Module imported with <code class="language-plaintext highlighter-rouge">using module</code></td>
      <td>✔️</td>
      <td>✔️</td>
    </tr>
  </tbody>
</table>

<p>If you use <code class="language-plaintext highlighter-rouge">Import-Module</code> to import the module, you can use the class/enum values implicitly, and autocomplete will work.
By implicitly, I mean that you can retrieve a class/enum instance from a module function, pass the instance around, modify the class properties, and pass it back into module function parameters.</p>

<p>You cannot use the class/enum type explicitly outside of the module though.
That is, you cannot create a new instance of the class, or reference the enum values directly, such as performing a <code class="language-plaintext highlighter-rouge">switch</code> statement on them.
As soon as you need to reference the class/enum name in your script (e.g. <code class="language-plaintext highlighter-rouge">[MyClass]</code> or <code class="language-plaintext highlighter-rouge">[MyEnum]</code>), you will get the <code class="language-plaintext highlighter-rouge">Unable to find type</code> error.</p>

<p>The only way to be able to reference the class/enum name outside of the module is to import the module with <code class="language-plaintext highlighter-rouge">using module</code>.
I did not explicitly test this using a binary module, but I expect the results would be the same.</p>

<h3 id="use-a-c-classenum-in-the-module-instead">Use a C# class/enum in the module instead</h3>

<p>Rather than using the PowerShell native classes and enums, we can define C# classes and enums inline in PowerShell as a string.
This even works in pre-PowerShell 5 versions.
It is a bit ugly, as you lose syntax highlighting and editor intellisense and checks, and you need to write the code as C# instead of PowerShell, but it does work.</p>

<p>Here is what the equivalent definition of the example PowerShell native class and enum shown earlier would look like when defining them as a C# class and enum in your PowerShell script or module:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Add</span><span class="p">-</span><span class="n">Type</span> <span class="p">-</span><span class="n">Language</span> <span class="n">CSharp</span> <span class="p">-</span><span class="n">TypeDefinition</span> <span class="s">@"
  public class MyClass {
      public string Name { get; set; }
      public int Age { get; set; }
  }

  public enum MyEnum {
      Value1,
      Value2
  }
"</span><span class="err">@</span>
</code></pre></div></div>

<p>We could optionally put our class and enum in a namespace as well (e.g. <code class="language-plaintext highlighter-rouge">MyNamespace</code>), and then reference their types in PowerShell like <code class="language-plaintext highlighter-rouge">[MyNamespace.MyClass]</code> and <code class="language-plaintext highlighter-rouge">[MyNamespace.MyEnum]</code>.
Using namespaces can help avoid naming conflicts for common class names.</p>

<p>Using C# classes and enums as shown above instead of the PowerShell native class/enum, the results are as follows:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>C# Class/Enum can be used by module functions</th>
      <th>C# Class/Enum type can be used outside of module</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Class/Enum file included with <code class="language-plaintext highlighter-rouge">using module</code></td>
      <td>✔️</td>
      <td>✔️</td>
    </tr>
    <tr>
      <td>Class/Enum file included with dot-sourcing</td>
      <td>✔️</td>
      <td>✔️</td>
    </tr>
    <tr>
      <td>Class/Enum defined in the psm1 file</td>
      <td>✔️</td>
      <td>✔️</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>C# Class/Enum can be used by module functions</th>
      <th>C# Class/Enum type can be used outside of module</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Module imported with <code class="language-plaintext highlighter-rouge">Import-Module</code></td>
      <td>✔️</td>
      <td>✔️</td>
    </tr>
    <tr>
      <td>Module imported with <code class="language-plaintext highlighter-rouge">using module</code></td>
      <td>✔️</td>
      <td>✔️</td>
    </tr>
  </tbody>
</table>

<p>You can see that using C# classes/enums is much more flexible than using the PowerShell native classes/enums.
They allow us to define the classes/enums in their own files, and allow end-users to use <code class="language-plaintext highlighter-rouge">Import-Module</code> and still have full access to the class/enum types.
The downsides are that they are a bit ugly as inline strings, and you need to know the C# syntax instead of PowerShell syntax.</p>

<h2 id="related-information">Related information</h2>

<h3 id="psframework">PSFramework</h3>

<p><a href="https://psframework.org">PSFramework</a> has some class-specific export options that you may be able to leverage when using PSFramework, as described in <a href="https://ruhr.social/@callidus2000/110876292213359073">this message</a>.
Even those solutions are not perfect though, as described in <a href="https://fosstodon.org/@jaykul/110879072765963081">this reply</a>.
I do not like having to depend on additional modules unless necessary, and prefer to use the PowerShell native methods since they will work everywhere.</p>

<h3 id="powershell-documentation">PowerShell documentation</h3>

<p>The PowerShell <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.3#importing-classes-from-a-powershell-module">class docs</a> and <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_using?view=powershell-7.3&amp;source=docs#module-syntax">using docs</a> say:</p>

<blockquote>
  <p>The using module statement imports classes from the root module (ModuleToProcess) of a script module or binary module.
It doesn’t consistently import classes defined in nested modules or classes defined in scripts that are dot-sourced into the module.
Classes that you want to be available to users outside of the module should be defined in the root module.</p>
</blockquote>

<p>Knowing what I know now, this makes sense.
When I first read it though, I thought when it said “the module” and “root module” it just meant any files in the module directory that may get pulled into the psm1 file, not specifically just the psm1 file.
I created <a href="https://github.com/MicrosoftDocs/PowerShell-Docs/pull/10343">this PR</a> to clarify this in the docs.</p>

<h3 id="powershell-classes-are-a-bit-of-a-mess">PowerShell classes are a bit of a mess</h3>

<p>As you can see from the need of this article, as well as all of the possible answers to <a href="https://stackoverflow.com/questions/31051103/how-to-export-a-class-in-a-powershell-v5-module">this Stack Overflow question</a> on how to export classes from a module, working with PowerShell native classes in modules is not as clear and straightforward as it could be.</p>

<p>Also, old class versions are kept in memory until the PowerShell session is restarted, which can make developing with classes a bit of a pain.
<a href="https://blog.danskingdom.com/Automatically-create-new-sessions-to-improve-PowerShell-development-with-classes-in-VS-Code/">Checkout this post</a> for how to have VS Code automatically create a new PowerShell session for you when you debug your script, so you don’t have to manually restart your session every time you make a change to your class.</p>

<p>Classes and enums are extremely useful, so I hope that PowerShell will continue to improve the experience of using them.</p>

<h3 id="create-your-module-in-c-instead-of-powershell">Create your module in C# instead of PowerShell</h3>

<p>It is possible to create modules and cmdlets entirely in C# instead of PowerShell.
This allows you to structure all of your code files however you want.
For more information on how to do this, <a href="https://blog.danskingdom.com/Create-and-test-PowerShell-Core-cmdlets-in-CSharp/">check out my other blog post</a>.</p>

<h3 id="powershell-class-definition-comparison">PowerShell class definition comparison</h3>

<p>I discuss the pros and cons of the different ways to define classes and enums in PowerShell <a href="https://blog.danskingdom.com/PowerShell-class-definition-pros-cons-and-performance-comparison/">in this post</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>In this post I’ve shown that to avoid headaches when using PowerShell native classes or enums in your PowerShell script modules, you should always:</p>

<ol>
  <li>Define the class/enum directly in the <code class="language-plaintext highlighter-rouge">.psm1</code> file.</li>
  <li>Import the module with <code class="language-plaintext highlighter-rouge">using module</code> instead of <code class="language-plaintext highlighter-rouge">Import-Module</code>.</li>
</ol>

<p>If you do not want to deal with the limitations of using PowerShell native classes/enums in your modules, then you can define them as C# classes/enums instead, avoiding potential problems, allowing you to put each class/enum in their own file, and providing a nicer experience for consumers of your module since they can still use the typical <code class="language-plaintext highlighter-rouge">Import-Module</code> command.</p>

<p>PowerShell 7.3.6 is the latest version at the time of writing this post.
Due of the nuances around using PowerShell native classes and enums in modules, they don’t quite feel like a complete first-class citizen yet.
Hopefully later versions of PowerShell will improve the language to make using PowerShell native classes and enums in modules easier and more straightforward with all the benefits of using C# classes/enums.</p>

<p>Happy scripting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="PowerShell" /><summary type="html"><![CDATA[I recently created a PowerShell script module that defines classes and enums. Everything worked fine locally, but broke when I tested the module on a build server. This was odd, as the module did not have any dependencies.]]></summary></entry><entry><title type="html">Inherit and extend a Blazor component, add UI elements, and hijack its parameters</title><link href="https://blog.danskingdom.com/Inherit-and-extend-a-Blazor-component-and-add-UI-elements-and-hijack-its-parameters/" rel="alternate" type="text/html" title="Inherit and extend a Blazor component, add UI elements, and hijack its parameters" /><published>2023-07-09T00:00:00+00:00</published><updated>2023-07-09T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Inherit-and-extend-a-Blazor-component-and-add-UI-elements-and-hijack-its-parameters</id><content type="html" xml:base="https://blog.danskingdom.com/Inherit-and-extend-a-Blazor-component-and-add-UI-elements-and-hijack-its-parameters/"><![CDATA[<p>In this post we see how to extend a Blazor component to show additional UI elements around it, how to override some of the default property settings, and how to hijack an EventCallback property and specify a new one for component consumers to use.</p>

<h2 id="the-use-case">The use case</h2>

<p>I have a lot of data grids in my Blazor app, and I always want to show a header on the grid that displays the number of rows in the grid, and buttons to export the grid to CSV and Excel.
Once I found myself copy-pasting the same code into 3 different pages, I figured it was time to refactor it into a new component.</p>

<p>For my specific app and in this example I’m using the <a href="https://blazor.radzen.com">Radzen Blazor Component’s</a> <a href="https://blazor.radzen.com/datagrid">DataGrid</a>, but the same principles apply to any Blazor component.</p>

<p><a href="https://github.com/radzenhq/radzen-blazor">Radzen Blazor</a> is a great component library for Blazor.
It is free, open source, and being updated all the time.
I highly recommend it.</p>

<h2 id="failed-attempt-contain-the-base-component-in-a-new-component">Failed attempt: Contain the base component in a new component</h2>

<p>My initial thought was to create a new component that simply contained a <code class="language-plaintext highlighter-rouge">RadzenDataGrid</code> component, and then add the header and button above that component.
So I figured the razor component code would look something like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"GridHeader"</span><span class="nt">&gt;</span>
  ... Display the header stuff here by referencing myGrid ...
<span class="nt">&lt;/div&gt;</span>

<span class="nt">&lt;RadzenDataGrid</span> <span class="err">@</span><span class="na">ref=</span><span class="s">"myGrid"</span> <span class="nt">/&gt;</span>

@code {
  private RadzenDataGrid<span class="nt">&lt;TItem&gt;</span> myGrid { get; set; }
  ... Figure out how to apply component parameters to myGrid ...
}
</code></pre></div></div>

<p>And then using the component would look something like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;MyRadzenDataGridWithHeader</span> <span class="na">Data=</span><span class="s">"productMappings"</span> <span class="na">TItem=</span><span class="s">"ProductMappingDto"</span> <span class="err">...</span> <span class="na">Other</span> <span class="na">RadzenDataGrid</span> <span class="na">properties</span> <span class="err">...</span><span class="nt">&gt;</span>
  <span class="nt">&lt;Columns&gt;</span>
    <span class="nt">&lt;RadzenDataGridColumn</span> <span class="na">Title=</span><span class="s">"Source Product ID"</span> <span class="na">Property=</span><span class="s">"Source_ProductIdentifier"</span> <span class="na">TItem=</span><span class="s">"ProductMappingDto"</span> <span class="nt">/&gt;</span>
    ... More columns ...
  <span class="nt">&lt;/Columns&gt;</span>
<span class="nt">&lt;/MyRadzenDataGridWithHeader&gt;</span>
</code></pre></div></div>

<p>This was problematic for a few reasons:</p>

<ol>
  <li>The <code class="language-plaintext highlighter-rouge">RadzenDataGrid</code> component has a generic type parameter, and I could not define a RadzenDataGrid component without providing the generic type.
I later found the <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/components/generic-type-support#typeparam-directive">@typeparam directive</a> which likely would have helped solved this problem, but I had moved on to a different solution by the time I found it.</li>
  <li>The <code class="language-plaintext highlighter-rouge">RadzenDataGrid</code> component has a lot of parameters, and I didn’t want to have to define all of them in my new component only to forward them to the RadzenDataGrid component.
One option I found was to use <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/splat-attributes-and-arbitrary-parameters">attribute splatting</a>, as mentioned in <a href="https://stackoverflow.com/a/73171242/602585">this StackOverflow answer</a>.
When using attribute splatting though, you lose the intellisense in the editor, so it would not have shown all of the parameters of the RadzenDataGrid component in my component.</li>
  <li>The <code class="language-plaintext highlighter-rouge">RadzenDataGrid</code> expects the columns to be defined as child elements, so I would have also had to figure out a way to forward those to the RadzenDataGrid component.
I suspect it could be done using the <code class="language-plaintext highlighter-rouge">ChildContent</code> property, as shown in <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/#child-content-render-fragments">the docs</a>, but I didn’t get that far.</li>
</ol>

<p>Before going too far down this path I found a better solution, which was to inherit the RadzenDataGrid component.</p>

<h2 id="better-solution-inherit-and-extend-the-base-component">Better solution: Inherit and extend the base component</h2>

<p>Rather than containing a RadzenDataGrid instance within a new component, I found I could inherit the RadzenDataGrid component and add my header and buttons to it.</p>

<h3 id="component-code">Component code</h3>

<p>I’ll just drop the entire code for my new component here, and then explain what’s going on piece by piece.
This is the code for my new component, <code class="language-plaintext highlighter-rouge">StandardRadzenDataGrid.razor</code>:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">@typeparam</span> <span class="n">TItem</span>
<span class="n">@inherits</span> <span class="n">RadzenDataGrid</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span>

<span class="p">&lt;!--</span> <span class="n">Grid</span> <span class="n">header</span> <span class="n">with</span> <span class="n">button</span> <span class="n">to</span> <span class="n">populate</span> <span class="n">grid</span><span class="p">,</span> <span class="n">show</span> <span class="n">the</span> <span class="n">number</span> <span class="n">of</span> <span class="n">rows</span><span class="p">,</span> <span class="n">and</span> <span class="n">buttons</span> <span class="n">to</span> <span class="n">export</span> <span class="n">to</span> <span class="n">CSV</span><span class="p">/</span><span class="n">Excel</span><span class="p">.</span> <span class="p">--&gt;</span>
<span class="p">&lt;</span><span class="n">div</span> <span class="n">style</span><span class="p">=</span><span class="s">"margin-bottom: 0.4rem;"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="n">RadzenButton</span> <span class="n">Click</span><span class="p">=</span><span class="s">"RetrieveGridData"</span> <span class="n">Text</span><span class="p">=</span><span class="s">"@RetrieveDataButtonText"</span> <span class="n">Disabled</span><span class="p">=</span><span class="s">"@(IsDisabled || IsLoading)"</span> <span class="p">/&gt;</span>

  <span class="p">&lt;</span><span class="n">span</span> <span class="n">style</span><span class="p">=</span><span class="s">"float:right;"</span><span class="p">&gt;</span>
    <span class="nf">@if</span> <span class="p">(</span><span class="n">numberOfRecords</span> <span class="p">&gt;=</span> <span class="m">0</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="p">&lt;</span><span class="n">span</span> <span class="n">style</span><span class="p">=</span><span class="s">"margin-right: 0.5rem;"</span><span class="p">&gt;</span><span class="n">Showing</span> <span class="n">@numberOfRecordsShown</span> <span class="n">of</span> <span class="n">@numberOfRecords</span> <span class="n">records</span><span class="p">&lt;/</span><span class="n">span</span><span class="p">&gt;</span>
    <span class="p">}</span>
    <span class="p">&lt;</span><span class="n">RadzenGridExportOptions</span> <span class="n">Grid</span><span class="p">=</span><span class="s">"this"</span> <span class="p">/&gt;</span>
  <span class="p">&lt;/</span><span class="n">span</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="n">div</span><span class="p">&gt;</span>

<span class="err">@</span><span class="p">{</span>
  <span class="c1">// Display the base RadzenDataGrid that we are inheriting from.</span>
  <span class="k">base</span><span class="p">.</span><span class="nf">BuildRenderTree</span><span class="p">(</span><span class="n">__builder</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">@code</span> <span class="p">{</span>
  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="n">EventCallback</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;</span> <span class="n">RetrieveData</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="kt">string</span> <span class="n">RetrieveDataButtonText</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="s">"Retrieve Data"</span><span class="p">;</span>

  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsDisabled</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>

  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="n">EventCallback</span><span class="p">&lt;</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;&gt;</span> <span class="n">FilterApplied</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

  <span class="c1">// By declaring this as new, and not using the Parameter attribute, we are hiding the base Filter property so it cannot be set on the component.</span>
  <span class="c1">// We hide the base Filter property so that we can add our own OnFilterChanged event handler to it, and not allow users to set it.</span>
  <span class="c1">// We instead expose a FilterApplied event that users can set on the component.</span>
  <span class="k">protected</span> <span class="k">new</span> <span class="n">EventCallback</span><span class="p">&lt;</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;&gt;</span> <span class="n">Filter</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

  <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnInitialized</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="k">base</span><span class="p">.</span><span class="nf">OnInitialized</span><span class="p">();</span>
    <span class="k">base</span><span class="p">.</span><span class="n">Filter</span> <span class="p">=</span> <span class="n">EventCallback</span><span class="p">.</span><span class="n">Factory</span><span class="p">.</span><span class="n">Create</span><span class="p">&lt;</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;&gt;(</span><span class="k">this</span><span class="p">,</span> <span class="n">OnFilterChanged</span><span class="p">);</span>

    <span class="nf">OverrideDefaultRadzenSettingsWithOnesWePrefer</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="k">private</span> <span class="k">void</span> <span class="nf">OverrideDefaultRadzenSettingsWithOnesWePrefer</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="n">AllowFiltering</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowSorting</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowMultiColumnSorting</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowColumnResize</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowColumnReorder</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="kt">int</span> <span class="n">numberOfRecords</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">numberOfRecordsShown</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>

  <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">RetrieveGridData</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="k">try</span>
    <span class="p">{</span>
      <span class="n">IsLoading</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>

      <span class="k">await</span> <span class="n">RetrieveData</span><span class="p">.</span><span class="nf">InvokeAsync</span><span class="p">();</span>

      <span class="n">numberOfRecords</span> <span class="p">=</span> <span class="n">Count</span><span class="p">;</span>
      <span class="n">numberOfRecordsShown</span> <span class="p">=</span> <span class="n">numberOfRecords</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">finally</span>
    <span class="p">{</span>
      <span class="n">IsLoading</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">OnFilterChanged</span><span class="p">(</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span> <span class="n">args</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">numberOfRecordsShown</span> <span class="p">=</span> <span class="n">View</span><span class="p">.</span><span class="nf">Count</span><span class="p">();</span>
    <span class="k">await</span> <span class="n">FilterApplied</span><span class="p">.</span><span class="nf">InvokeAsync</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="inheriting-the-base-component">Inheriting the base component</h4>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">@typeparam</span> <span class="n">TItem</span>
<span class="n">@inherits</span> <span class="n">RadzenDataGrid</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>The <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/#specify-a-base-class">@inherits directive</a> allows us to inherit from the RadzenDataGrid component.
Since the RadzenDataGrid component has a generic type parameter, we need to specify that type parameter in our component as well, which is what the <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/generic-type-support">@typeparam directive</a> is for.</p>

<p>The above razor code is the equivalent to doing this in C#:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">StandardRadzenDataGrid</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">RadzenDataGrid</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>Just like with C# code, you can only inherit from a component if it has not been marked as <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed">sealed</a>.</p>

<h4 id="adding-our-own-header-and-buttons">Adding our own header and buttons</h4>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;!--</span> <span class="n">Grid</span> <span class="n">header</span> <span class="n">with</span> <span class="n">button</span> <span class="n">to</span> <span class="n">populate</span> <span class="n">grid</span><span class="p">,</span> <span class="n">show</span> <span class="n">the</span> <span class="n">number</span> <span class="n">of</span> <span class="n">rows</span><span class="p">,</span> <span class="n">and</span> <span class="n">buttons</span> <span class="n">to</span> <span class="n">export</span> <span class="n">to</span> <span class="n">CSV</span><span class="p">/</span><span class="n">Excel</span><span class="p">.</span> <span class="p">--&gt;</span>
<span class="p">&lt;</span><span class="n">div</span> <span class="n">style</span><span class="p">=</span><span class="s">"margin-bottom: 0.4rem;"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="n">RadzenButton</span> <span class="n">Click</span><span class="p">=</span><span class="s">"RetrieveGridData"</span> <span class="n">Text</span><span class="p">=</span><span class="s">"@RetrieveDataButtonText"</span> <span class="n">Disabled</span><span class="p">=</span><span class="s">"@(IsDisabled || IsLoading)"</span> <span class="p">/&gt;</span>

  <span class="p">&lt;</span><span class="n">span</span> <span class="n">style</span><span class="p">=</span><span class="s">"float:right;"</span><span class="p">&gt;</span>
    <span class="nf">@if</span> <span class="p">(</span><span class="n">numberOfRecords</span> <span class="p">&gt;=</span> <span class="m">0</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="p">&lt;</span><span class="n">span</span> <span class="n">style</span><span class="p">=</span><span class="s">"margin-right: 0.5rem;"</span><span class="p">&gt;</span><span class="n">Showing</span> <span class="n">@numberOfRecordsShown</span> <span class="n">of</span> <span class="n">@numberOfRecords</span> <span class="n">records</span><span class="p">&lt;/</span><span class="n">span</span><span class="p">&gt;</span>
    <span class="p">}</span>
    <span class="p">&lt;</span><span class="n">RadzenGridExportOptions</span> <span class="n">Grid</span><span class="p">=</span><span class="s">"this"</span> <span class="p">/&gt;</span>
  <span class="p">&lt;/</span><span class="n">span</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="n">div</span><span class="p">&gt;</span>
<span class="p">...</span>
<span class="n">@code</span> <span class="p">{</span>
  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="n">EventCallback</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;</span> <span class="n">RetrieveData</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="kt">string</span> <span class="n">RetrieveDataButtonText</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="s">"Retrieve Data"</span><span class="p">;</span>

  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="kt">bool</span> <span class="n">IsDisabled</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="p">...</span>
  <span class="kt">int</span> <span class="n">numberOfRecords</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">numberOfRecordsShown</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>

  <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">RetrieveGridData</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="k">try</span>
    <span class="p">{</span>
      <span class="n">IsLoading</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>

      <span class="k">await</span> <span class="n">RetrieveData</span><span class="p">.</span><span class="nf">InvokeAsync</span><span class="p">();</span>

      <span class="n">numberOfRecords</span> <span class="p">=</span> <span class="n">Count</span><span class="p">;</span>
      <span class="n">numberOfRecordsShown</span> <span class="p">=</span> <span class="n">numberOfRecords</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">finally</span>
    <span class="p">{</span>
      <span class="n">IsLoading</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>The code above is adding the header functionality I wanted on all my grids.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;RadzenButton</span> <span class="na">Click=</span><span class="s">"RetrieveGridData"</span> <span class="na">Text=</span><span class="s">"@RetrieveDataButtonText"</span> <span class="na">Disabled=</span><span class="s">"@(IsDisabled || IsLoading)"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>This line displays a button that the user will click to populate the grid with data.</p>

<p>The <code class="language-plaintext highlighter-rouge">RetrieveGridData</code> method will be called when the button is clicked.
The method calls a user-provided method, <code class="language-plaintext highlighter-rouge">RetrieveData</code>, which is passed in as a parameter to the component.
<code class="language-plaintext highlighter-rouge">RetrieveData</code> will do the actual work of retrieving the data and populating the grid.</p>

<p>The <code class="language-plaintext highlighter-rouge">IsDisabled</code> parameter allows the user to specify if the button should be disabled or not.
e.g. If the user has not provided all of the required parameters for the grid to be populated, then the button should be disabled.
We also disable it automatically while the grid is being populated, via the <code class="language-plaintext highlighter-rouge">IsLoading</code> property provided by the <code class="language-plaintext highlighter-rouge">RadzenDataGrid</code>, so that the user cannot click it again while it is still working.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;span</span> <span class="na">style=</span><span class="s">"margin-right: 0.5rem;"</span><span class="nt">&gt;</span>Showing @numberOfRecordsShown of @numberOfRecords records<span class="nt">&lt;/span&gt;</span>
</code></pre></div></div>

<p>This line displays the number of records that are currently being shown in the grid, and the total number of records that are available.
You can see that these variables are set in the <code class="language-plaintext highlighter-rouge">RetrieveGridData</code> method after the users <code class="language-plaintext highlighter-rouge">RetrieveData</code> method has been called.
Initially, all records are shown.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;RadzenGridExportOptions</span> <span class="na">Grid=</span><span class="s">"this"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>This line displays the CSV and Excel export buttons for the grid.
Here I’m using the <a href="https://github.com/Inspirare-LLC/Radzen.Blazor.GridExportOptions">Radzen.Blazor.GridExportOptions</a> 3rd party control.
If you do not want to use a 3rd party library or need more control over the export, <a href="https://blazor.radzen.com/export-excel-csv">the Blazor docs</a> show how <a href="https://github.com/radzenhq/radzen-blazor/blob/master/RadzenBlazorDemos.Host/Controllers/ExportController.cs">the open source code</a> handles the exports, which you can copy and paste into your own project.</p>

<h4 id="displaying-the-base-component">Displaying the base component</h4>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">@</span><span class="p">{</span>
  <span class="c1">// Display the base RadzenDataGrid that we are inheriting from.</span>
  <span class="k">base</span><span class="p">.</span><span class="nf">BuildRenderTree</span><span class="p">(</span><span class="n">__builder</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is how we display the base RadzenDataGrid that we are inheriting from.
We place it after the header code so that it is displayed under the header.</p>

<p><a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-6.0#specify-a-base-class">The docs for inheriting from a base class</a> do not mention how to display the base component, so this was one of the trickier parts for me to figure out.
Thankfully it is just one line of code though.</p>

<p><a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/advanced-scenarios#manually-build-a-render-tree-rendertreebuilder">The advanced scenarios docs</a> say that you need to be careful when using the RenderTreeBuilder.
Here we are just displaying the base component though, not changing the implementation of how it gets rendered, so it is safe to do.</p>

<h4 id="hiding-the-base-filter-parameter">Hiding the base Filter parameter</h4>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="p">[</span><span class="n">Parameter</span><span class="p">]</span>
  <span class="k">public</span> <span class="n">EventCallback</span><span class="p">&lt;</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;&gt;</span> <span class="n">FilterApplied</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

  <span class="c1">// By declaring this as new, and not using the Parameter attribute, we</span>
  <span class="c1">// are hiding the base Filter property so it cannot be set on the component.</span>
  <span class="c1">// We hide the base Filter property so that we can add our own OnFilterChanged</span>
  <span class="c1">// event handler to it, and not allow users to set it.</span>
  <span class="c1">// We instead expose a FilterApplied event that users can set on the component.</span>
  <span class="k">protected</span> <span class="k">new</span> <span class="n">EventCallback</span><span class="p">&lt;</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;&gt;</span> <span class="n">Filter</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

  <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnInitialized</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="k">base</span><span class="p">.</span><span class="nf">OnInitialized</span><span class="p">();</span>
    <span class="k">base</span><span class="p">.</span><span class="n">Filter</span> <span class="p">=</span> <span class="n">EventCallback</span><span class="p">.</span><span class="n">Factory</span><span class="p">.</span><span class="n">Create</span><span class="p">&lt;</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;&gt;(</span><span class="k">this</span><span class="p">,</span> <span class="n">OnFilterChanged</span><span class="p">);</span>
    <span class="p">...</span>
  <span class="p">}</span>
<span class="p">...</span>
  <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">OnFilterChanged</span><span class="p">(</span><span class="n">DataGridColumnFilterEventArgs</span><span class="p">&lt;</span><span class="n">TItem</span><span class="p">&gt;</span> <span class="n">args</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">numberOfRecordsShown</span> <span class="p">=</span> <span class="n">View</span><span class="p">.</span><span class="nf">Count</span><span class="p">();</span>
    <span class="k">await</span> <span class="n">FilterApplied</span><span class="p">.</span><span class="nf">InvokeAsync</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">RadzenDataGrid</code> has a <code class="language-plaintext highlighter-rouge">Filter</code> EventCallback property that allows you to set an event handler that will be called when the user changes the filter.
We want to hook into this event so that we can update the number of records shown in the header when the user filters the grid.
Unlike regular C# events which can handle multiple event handlers by using the <code class="language-plaintext highlighter-rouge">+=</code> operator, only one event handler can be assigned on <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-7.0#eventcallback">an EventCallback property</a>.
This means that we need to prevent users from setting their own event handler on the <code class="language-plaintext highlighter-rouge">Filter</code> property, and instead expose our own <code class="language-plaintext highlighter-rouge">FilterApplied</code> event that users can set.</p>

<p>We hide the base <code class="language-plaintext highlighter-rouge">Filter</code> property by declaring our own <code class="language-plaintext highlighter-rouge">Filter</code> property as <code class="language-plaintext highlighter-rouge">new</code>, and <em>not</em> using the <code class="language-plaintext highlighter-rouge">Parameter</code> attribute.
This means that the base <code class="language-plaintext highlighter-rouge">Filter</code> property cannot be set on our component.
We instead expose a <code class="language-plaintext highlighter-rouge">FilterApplied</code> EventCallback that users can set on the component.</p>

<p>Lastly, in the <code class="language-plaintext highlighter-rouge">OnInitialized</code> method, we set the base <code class="language-plaintext highlighter-rouge">Filter</code> property to call our own <code class="language-plaintext highlighter-rouge">OnFilterChanged</code> method when the user changes the filter, which in turn triggers the <code class="language-plaintext highlighter-rouge">FilterApplied</code> event.</p>

<h4 id="specifying-our-own-default-property-values-for-the-base-component">Specifying our own default property values for the base component</h4>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnInitialized</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="k">base</span><span class="p">.</span><span class="nf">OnInitialized</span><span class="p">();</span>
    <span class="p">...</span>
    <span class="nf">OverrideDefaultRadzenSettingsWithOnesWePrefer</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="k">private</span> <span class="k">void</span> <span class="nf">OverrideDefaultRadzenSettingsWithOnesWePrefer</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="n">AllowFiltering</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowSorting</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowMultiColumnSorting</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowColumnResize</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
    <span class="n">AllowColumnReorder</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>There were a few default settings that I found myself setting on every grid, so I decided to set them as the default values for the component.</p>

<p>The settings I changed are in the <code class="language-plaintext highlighter-rouge">OverrideDefaultRadzenSettingsWithOnesWePrefer</code> function, which is called from the <code class="language-plaintext highlighter-rouge">OnInitialized</code> method.
This approach means I no longer need to set these parameters on every grid, but they can still be overridden using the parameters as usual when using the StandardRadzenDataGrid component if required.</p>

<h3 id="using-our-new-component">Using our new component</h3>

<p>Now that we have our new component, we can use it in our Blazor pages.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">@page</span> <span class="s">"/get-product-mappings"</span>
<span class="n">@using</span> <span class="n">App</span><span class="p">.</span><span class="n">ProductMappings</span>
<span class="n">@inject</span> <span class="n">SettingsService</span> <span class="n">SettingsService</span>

<span class="p">&lt;</span><span class="n">StandardRadzenDataGrid</span> <span class="n">Data</span><span class="p">=</span><span class="s">"productMappings"</span> <span class="n">TItem</span><span class="p">=</span><span class="s">"ProductMappingDto"</span>
        <span class="n">RetrieveData</span><span class="p">=</span><span class="s">"RetrieveProductMappings"</span> <span class="n">RetrieveDataButtonText</span><span class="p">=</span><span class="s">"Retrieve Product Mappings"</span>
        <span class="n">IsDisabled</span><span class="p">=</span><span class="s">"@(!SettingsService.SettingsAreValid)"</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="n">Columns</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="n">RadzenDataGridColumn</span> <span class="n">Title</span><span class="p">=</span><span class="s">"Source Product Name"</span> <span class="n">Property</span><span class="p">=</span><span class="s">"Source_ProductName"</span> <span class="n">TItem</span><span class="p">=</span><span class="s">"ProductMappingDto"</span> <span class="p">/&gt;</span>
    <span class="p">&lt;</span><span class="n">RadzenDataGridColumn</span> <span class="n">Title</span><span class="p">=</span><span class="s">"Destination Product Name"</span> <span class="n">Property</span><span class="p">=</span><span class="s">"Destination_ProductName"</span> <span class="n">TItem</span><span class="p">=</span><span class="s">"ProductMappingDto"</span> <span class="p">/&gt;</span>
  <span class="p">&lt;/</span><span class="n">Columns</span><span class="p">&gt;</span>
<span class="p">&lt;/</span><span class="n">StandardRadzenDataGrid</span><span class="p">&gt;</span>

<span class="n">@code</span> <span class="p">{</span>
  <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">ProductMappingDto</span><span class="p">&gt;</span> <span class="n">productMappings</span> <span class="p">=</span> <span class="n">Enumerable</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="n">ProductMappingDto</span><span class="p">&gt;();</span>

  <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">RetrieveProductMappings</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="n">productMappings</span> <span class="p">=</span> <span class="k">await</span> <span class="n">GetProductMappingQuery</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">(</span><span class="n">SettingsService</span><span class="p">.</span><span class="n">Settings</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In the code above you can see that we are using our new <code class="language-plaintext highlighter-rouge">StandardRadzenDataGrid</code> component.
We provide the <code class="language-plaintext highlighter-rouge">RetrieveProductMappings</code> method to the <code class="language-plaintext highlighter-rouge">RetrieveData</code> parameter, which will be called when the user clicks the button to retrieve the data.</p>

<p>We use the <code class="language-plaintext highlighter-rouge">RetrieveDataButtonText</code> parameter to specify what text should appear on the button, and have set the <code class="language-plaintext highlighter-rouge">IsDisabled</code> parameter to <code class="language-plaintext highlighter-rouge">true</code> when the settings are not valid for the Get Product Mappings query to be performed.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We have seen how we can inherit and extend an existing component to create our own component.
We are able to add new UI elements to it, override default values of the base component, and even hide parameters of the base component so that users cannot set them.</p>

<p>Creating our own component in this way allows us to reduce the amount of code we need to write in our Blazor pages, and also allows us to enforce certain settings on the component that we want to be consistent across our application.</p>

<p>If you found this article helpful, or have any other thoughts on how this could be improved or different approaches that could be taken, leave a comment below.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Blazor" /><category term="CSharp" /><category term="Blazor" /><category term="CSharp" /><summary type="html"><![CDATA[In this post we see how to extend a Blazor component to show additional UI elements around it, how to override some of the default property settings, and how to hijack an EventCallback property and specify a new one for component consumers to use.]]></summary></entry><entry><title type="html">Update your terminal prompt and font in Windows Terminal, VS Code, and Visual Studio</title><link href="https://blog.danskingdom.com/Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/" rel="alternate" type="text/html" title="Update your terminal prompt and font in Windows Terminal, VS Code, and Visual Studio" /><published>2023-06-29T00:00:00+00:00</published><updated>2023-06-29T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio</id><content type="html" xml:base="https://blog.danskingdom.com/Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/"><![CDATA[<p>I use <a href="https://ohmyposh.dev">Oh My Posh</a> to improve my PowerShell terminal’s prompt appearance and show additional helpful information.
There are <a href="https://ohmyposh.dev/docs/themes">many themes to choose from</a>, and I have even created <a href="https://github.com/deadlydog/Oh-My-Posh.DeadlydogTheme">my own theme</a> that you are welcome to use.
Mine looks like this:</p>

<p><img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/deadlydog-theme-screenshot.png" alt="Oh My Posh deadlydog theme screenshot" /></p>

<p>You can see it displays additional helpful information, like the current directory, the current git branch and status, how long it took the last command to complete, and more.</p>

<p>To use a terminal prompt theme you need to have a font installed that supports all the icons that the theme uses, and then configure your terminal to use that font.</p>

<p>Even if you do not want to use a prompt theme, you may still want to update your terminal font, so let’s see how to do that.</p>

<h2 id="download-and-install-a-font">Download and install a font</h2>

<p>The first step is to install a font that supports all the icons you want to use.
You can head over to <a href="https://www.nerdfonts.com/font-downloads">https://www.nerdfonts.com/font-downloads</a> and find a font that you like and supports the icons you want.
I personally like the <code class="language-plaintext highlighter-rouge">CaskaydiaCove Nerd Font</code>.</p>

<p>Once you’ve downloaded and unzipped the font, you can double-click the appropriate .ttf file to open it in the Windows Font Viewer, and then click the <code class="language-plaintext highlighter-rouge">Install</code> button to install it.</p>

<p>Here is a screenshot of installing the <code class="language-plaintext highlighter-rouge">CaskaydiaCoveNerdFontMono-Regular.ttf</code> file font:</p>

<p><img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/install-font-screenshot.png" alt="Installing a font" /></p>

<p>You can install several fonts if you like so that you have a few to choose from in your applications.
If you want to install many fonts at once, you can select multiple .ttf files and then right-click and select <code class="language-plaintext highlighter-rouge">Install</code> from the context menu.</p>

<p>I personally like <code class="language-plaintext highlighter-rouge">Mono</code> fonts for my code and terminal, as I find them easier to read and better for aligning code.
Mono fonts use the same width for all characters (e.g. the <code class="language-plaintext highlighter-rouge">i</code> character will take the same amount of space as the <code class="language-plaintext highlighter-rouge">w</code> character).
I will be configuring my terminal to use the <code class="language-plaintext highlighter-rouge">CaskaydiaCove Nerd Font Mono</code> font in the examples below.</p>

<h2 id="update-windows-terminal-to-use-the-new-font">Update Windows Terminal to use the new font</h2>

<p>To update the Windows Terminal font:</p>

<ol>
  <li>Open Windows Terminal.</li>
  <li>Open the Settings via the tab dropdown menu, or by pressing <kbd>Ctrl</kbd>+<kbd>,</kbd>.</li>
  <li>(Optional) I recommend setting the <code class="language-plaintext highlighter-rouge">Default terminal application</code> to Windows Terminal.
This will make it so that when Windows launches a terminal app, such as <code class="language-plaintext highlighter-rouge">cmd</code>, <code class="language-plaintext highlighter-rouge">Windows PowerShell</code>, <code class="language-plaintext highlighter-rouge">PowerShell 7</code>, or the Visual Studio debugging console, it will launch in Windows Terminal instead of their default (old-style) console.
<img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/set-windows-terminal-as-default-terminal.png" alt="Setting Windows Terminal as the default terminal application" /></li>
  <li>Select the profile that you want to change the font for.
If you want to change the default font for all profiles, then select the <code class="language-plaintext highlighter-rouge">Defaults</code> profile.</li>
  <li>Click into the <code class="language-plaintext highlighter-rouge">Appearance</code> section.
<img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/access-windows-terminal-defaults-appearance-screenshot.png" alt="Select default profile appearance" /></li>
  <li>Change the <code class="language-plaintext highlighter-rouge">Font face</code> to the font you want to use.
<img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/windows-terminal-set-font-screenshot.png" alt="Select font face" /></li>
  <li>Save the changes.</li>
  <li>Repeat for any other profiles you want to change the font for.</li>
</ol>

<p>Windows terminal should now be using your new font 🙌.</p>

<h2 id="update-the-vs-code-terminal-to-use-the-new-font">Update the VS Code terminal to use the new font</h2>

<p>To update the VS Code terminal font:</p>

<ol>
  <li>Open Visual Studio Code.</li>
  <li>Navigate to <code class="language-plaintext highlighter-rouge">File</code> -&gt; <code class="language-plaintext highlighter-rouge">Preferences</code> -&gt; <code class="language-plaintext highlighter-rouge">Settings</code>, or press <kbd>Ctrl</kbd>+<kbd>,</kbd> to open the Settings window.</li>
  <li>Search for <code class="language-plaintext highlighter-rouge">font family</code>.</li>
  <li>You will likely notice the <code class="language-plaintext highlighter-rouge">Editor: Font Family</code> setting near the top.
You can change this if you like, but it will change the code editor font, not the terminal font.
<img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/vs-code-change-editor-font-screenshot.png" alt="Change VS Code editor font family" /></li>
  <li>Scroll down to the <code class="language-plaintext highlighter-rouge">Terminal &gt; Integrated: Font Family</code> setting, and update it to the font you want to use.
<img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/vs-code-change-terminal-font-screenshot.png" alt="Change VS Code terminal font family" /></li>
  <li>Save the changes.</li>
</ol>

<p>The VS Code terminal should now be using your new font 🙌.</p>

<h2 id="update-the-visual-studio-terminal-to-use-the-new-font">Update the Visual Studio terminal to use the new font</h2>

<p>To update the Visual Studio terminal font:</p>

<ol>
  <li>Open Visual Studio.</li>
  <li>Navigate to <code class="language-plaintext highlighter-rouge">Tools</code> -&gt; <code class="language-plaintext highlighter-rouge">Options</code> to open the Options window.</li>
  <li>Navigate to the <code class="language-plaintext highlighter-rouge">Environment</code> -&gt; <code class="language-plaintext highlighter-rouge">Fonts and Colors</code> section.</li>
  <li>By default it will be on the <code class="language-plaintext highlighter-rouge">Text Editor</code> setting.
 You can change the font for these items if you like, but it will change the code editor font, not the terminal font.</li>
  <li>Change the setting to <code class="language-plaintext highlighter-rouge">Terminal</code>.</li>
  <li>Update the <code class="language-plaintext highlighter-rouge">Font</code> setting to the font you want to use.
<img src="/assets/Posts/2023-06-29-Update-your-terminal-prompt-and-font-in-Windows-Terminal-and-VS-Code-and-Visual-Studio/set-visual-studio-terminal-font-screenshot.png" alt="Change Visual Studio terminal font" /></li>
  <li>Click the OK button to save the changes.</li>
</ol>

<p>The Visual Studio terminal should now be using your new font 🙌.</p>

<h2 id="conclusion">Conclusion</h2>

<p>You now know about Oh My Posh, terminal prompt themes, and how to update the fonts in Windows Terminal, VS Code, and Visual Studio.
I hope you found this post helpful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Editor" /><category term="PowerShell" /><category term="Visual Studio" /><category term="Visual Studio Code" /><category term="Windows Terminal" /><category term="Editor" /><category term="PowerShell" /><category term="Visual Studio" /><category term="Visual Studio Code" /><category term="Windows Terminal" /><summary type="html"><![CDATA[I use Oh My Posh to improve my PowerShell terminal’s prompt appearance and show additional helpful information. There are many themes to choose from, and I have even created my own theme that you are welcome to use. Mine looks like this:]]></summary></entry><entry><title type="html">Increase system fault tolerance with the Stale Cache pattern</title><link href="https://blog.danskingdom.com/Increase-system-fault-tolerance-with-the-Stale-Cache-pattern/" rel="alternate" type="text/html" title="Increase system fault tolerance with the Stale Cache pattern" /><published>2023-06-27T00:00:00+00:00</published><updated>2023-12-15T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Increase-system-fault-tolerance-with-the-Stale-Cache-pattern</id><content type="html" xml:base="https://blog.danskingdom.com/Increase-system-fault-tolerance-with-the-Stale-Cache-pattern/"><![CDATA[<p>Caching is used to improve the performance of an application.
When applied properly, caches can also help increase an application’s fault tolerance, helping to prevent outages and improve the user experience.</p>

<p>In this post I introduce the <code class="language-plaintext highlighter-rouge">Stale Cache</code> pattern and show how to apply it to time-based caches to further increase the fault tolerance of your applications.
This pattern has helped prevent production outages in several systems I’ve worked on, and I hope it can help you too.</p>

<h2 id="tldr">TL;DR</h2>

<p>The <code class="language-plaintext highlighter-rouge">Stale Cache</code> pattern retains stale cache items and returns them if needed until they can be replaced with fresh items.
It is not a replacement for other time-based caching strategies, but rather an enhancement that can be applied to them to help make your applications and systems more fault tolerant.</p>

<h2 id="what-is-a-cache">What is a cache?</h2>

<p><a href="https://en.wikipedia.org/wiki/Cache_(computing)">Wikipedia defines a cache</a> as,</p>

<blockquote>
  <p>A hardware or software component that stores data so that future requests for that data can be served faster.</p>
</blockquote>

<p>Basically, a cache is a place to store data that is used frequently so that it can be accessed faster than if it were retrieved (or calculated) from the original source every time, improving the speed of the application.</p>

<p>Caching is a huge topic with many considerations, such as:</p>

<ul>
  <li>Where in your application stack should you do the caching? In the client application, the web server, the database, or a separate component such as a Redis server or CDN.</li>
  <li>What type of cache to use? In-memory, disk, or distributed.</li>
  <li>What caching strategy to use? Cache-aside, read-through, write-through, write-around, write-back, refresh-ahead, etc.</li>
  <li>How do you handle cache invalidation? Expiration (absolute time, sliding window, etc.), eviction (least recently used, least frequently used, random, etc.), or something else.</li>
</ul>

<p>Depending on your application, you may implement several different caches at different layers, each with different strategies and configurations.</p>

<p>There are tons of great articles on caching and the pros and cons of various strategies, like <a href="https://dev.to/kalkwst/database-caching-strategies-16in">this</a> and <a href="https://medium.com/geekculture/overview-of-caching-distributed-cache-caching-patterns-techniques-6130a116820">this</a> and <a href="https://levelup.gitconnected.com/6-caching-strategies-for-system-design-interviews-8cf22193b360">this</a> and <a href="https://www.neovolve.com/2008/10/08/cache-expiration-policies/">this</a> and <a href="https://www.linkedin.com/pulse/exploring-caching-patterns-microservices-architecture-saeed-anabtawi/">this</a> and <a href="https://hazelcast.com/blog/a-hitchhikers-guide-to-caching-patterns/">this</a>, so I won’t go into any more detail here.
I make reference to the cache-aside, read-through, and refresh-ahead strategies later, so you can see these resources for more information on them if needed.
Remember to finish this article first before falling down the external links rabbit hole 😅.</p>

<h2 id="what-is-fault-tolerance">What is fault tolerance?</h2>

<p><a href="https://en.wikipedia.org/wiki/Fault_tolerance">Wikipedia defines Fault tolerance</a> as,</p>

<blockquote>
  <p>The ability of a system to continue operating properly in the event of the failure of some of its components.
…
A fault-tolerant design enables a system to continue its intended operation, possibly at a reduced level, rather than failing completely, when some part of the system fails.</p>
</blockquote>

<p>In this article, I’m focusing on having your application remain operational when an external service that it retrieves data from is unavailable, such as a database or web service.</p>

<h2 id="what-is-the-stale-cache-pattern">What is the Stale Cache pattern?</h2>

<h3 id="story-time">Story time</h3>

<p>Let’s illustrate the pattern through a short story.</p>

<p>Imagine you want to make a sandwich.
You go to your kitchen and get some bread.
You notice that while the bread is not moldy, it has passed its best-before date and is a bit stale.
Fresh bread always makes a sandwich taste way better, so you toss the stale bread in the garbage and walk to the bakery to get some fresh bread.
When you arrive at the bakery you find they are closed because a water pipe burst and they will not be open until tomorrow.
There are no other bakeries nearby, so you go back home.
Now you are home, still hungry, and unable to make a sandwich because you have no bread.</p>

<p>You might say, well just pull the stale bread out of the garbage and make a sandwich with that.
Even if you are the type to pull food out of the garbage (<a href="https://www.youtube.com/watch?v=t36jwyVncmQ">like George Costanza</a> 😂), in this hypothetical scenario let’s say that your roommate already <em>collected the garbage</em> and took it out (see what I did there 😛), so the bread really is gone.</p>

<p>With no bread for a sandwich and your stomach growling, you reflect on what you could have done to avoid this unhappy situation.
The answer is simple; you should not have thrown out the stale bread until you had fresh bread to replace it.
This is the <code class="language-plaintext highlighter-rouge">Stale Cache</code> pattern.</p>

<h3 id="how-is-this-different-from-traditional-time-based-caching">How is this different from traditional time-based caching?</h3>

<p>Most time-based caching implementations only allow a single expiration time.
When the expiration time is reached, the item is removed from the cache.
The next time something tries to access the item, it is not found in the cache and the item must be retrieved from the source, such as a database or another web service.
If the source is unavailable, then the item cannot be retrieved and the application fails or has to perform some additional compensating action.</p>

<p>By holding on to the stale cache item until a fresh item is available, the cache can return the stale item to the caller and the application can continue to function even when the source is unavailable, making the application more fault tolerant.</p>

<blockquote>
  <p>Retaining stale cache items until they can be replaced with fresh ones makes your application more fault tolerant.</p>
</blockquote>

<h3 id="cache-item-staleness-and-expiry">Cache item staleness and expiry</h3>

<p>You likely want to remove stale items from the cache eventually though, as using data that is too old may cause other problems in your application.
Just like how you wouldn’t want to make a sandwich with bread that was molding, you may not want to use data that is too old.
We call the date that the cached item is no longer safe to use the Expiry date.</p>

<p>There are 3 states for a cache item in the Stale Cache pattern:</p>

<ul>
  <li><strong>Fresh</strong>: The item has not yet reached its Stale (or best-before) date.
The item is not stale yet and can be used without any additional actions.</li>
  <li><strong>Stale</strong>: The item has passed its Stale date, but not its Expiry date.
The item is stale, but can still be used safely.
A fresh item should be requested from the source and the cache updated with the fresh item.</li>
  <li><strong>Expired</strong>: The item has passed both its Stale and Expiry date.
The item is expired and cannot be used safely.
The expired item should be removed from the cache and discarded.
A fresh item should be requested from the source and the cache updated with the fresh item.</li>
</ul>

<p>As with any cache, you still need to be mindful of how long in the future you set the Stale and Expiry dates.
Different pieces of data likely have different freshness requirements.</p>

<h3 id="how-it-improves-fault-tolerance">How it improves fault tolerance</h3>

<p>The image below shows an example of how the Stale Cache pattern can be leveraged in a cache-aside strategy, allowing the application to continue to function even when the external source is unavailable.</p>

<p><img src="/assets/Posts/2023-06-27-Increase-system-fault-tolerance-with-the-Stale-Cache-pattern/stale-cache-pattern-with-synchronous-cache-aside-strategy.drawio.png" alt="Cache-aside strategy using the Stale Cache pattern where application works even when the source is not available" /></p>

<p>In an ideal scenario:</p>

<ul>
  <li>Step 2 would have returned a fresh item from the cache and there would be no need to make a request to the external source, or</li>
  <li>Step 3 would have reached the external source and a fresh item would have been returned to the application.
The application then could have updated the cache with the fresh item, and then used it for the current request.</li>
</ul>

<p>The Stale Cache pattern allows the application to get the best of both worlds; use a fresh item when available, but still be able to function when the external source is unavailable.</p>

<h3 id="preferring-speed-over-freshness">Preferring speed over freshness</h3>

<p>In our earlier story, you may have been too hungry to wait for fresh bread.
An alternative would have been to make your sandwich using the stale bread, and ask your roommate to go to the bakery for fresh bread while you eat.
When your roommate returns with the fresh bread, you can throw out the stale bread and use the fresh bread for your next sandwich.</p>

<p>This is another way to implement the Stale Cache pattern, where you return the stale cache item right away to improve speed, and request a fresh item in the background, allowing the cache to be updated with the fresh item asynchronously so it can be used for future cache requests.</p>

<p><img src="/assets/Posts/2023-06-27-Increase-system-fault-tolerance-with-the-Stale-Cache-pattern/stale-cache-pattern-with-asynchronous-cache-aside-strategy.drawio.png" alt="Cache-aside strategy using the Stale Cache pattern asynchronously to improve application speed" /></p>

<p>In our example above, the cache may have been in-memory or distributed.
A different strategy could be used in place of the cache-aside strategy, such as the read-through strategy.
Those changes may require slight implementation changes, but the idea of the Stale Cache pattern remains the same; use both a Stale and Expiry date to allow stale, but still safe, data to be used when fresher data is not available.</p>

<h2 id="when-to-use-the-stale-caching-pattern">When to use the Stale caching pattern</h2>

<h3 id="--enhance-existing-caching-strategies">- Enhance existing caching strategies</h3>

<p>The Stale Cache pattern is not meant to replace any caching strategies, but rather is meant to be used in conjunction with them, when applicable.
This is shown in the examples above where it is used with the cache-aside strategy.</p>

<h3 id="--enhance-existing-fault-tolerance-strategies">- Enhance existing fault tolerance strategies</h3>

<p>Similarly, the Stale Cache pattern is not meant to replace other fault tolerance strategies, like retries.
They can be used together to make your application even more fault tolerant.
For example, you may return a stale cache item while also retrying failed requests to the source multiple times in the background to retrieve a fresh item.</p>

<h3 id="--when-cache-item-eviction-is-based-on-age">- When cache item eviction is based on age</h3>

<p>The Stale Cache pattern is by nature centered around time expiration, so it is not applicable when using non-time based cache invalidation strategies, such as least-recently-used or least-frequently-used eviction algorithms where items are evicted when the cache reaches a certain size, rather than when each cache item reaches a certain age.</p>

<h3 id="--read-only-operations">- Read-only operations</h3>

<p>The Stale Cache pattern is only applicable to read operations, not write operations.</p>

<h3 id="--can-improve-speed-at-the-expense-accuracy">- Can improve speed at the expense accuracy</h3>

<p>The Stale Cache pattern is a good fit for applications that value speed over accuracy.
As shown above, the implementation may allow applications to return stale cached data right away and then fetch updated data in the background.
Be aware though that retrieving data asynchronously in the background may come at the expense of more complicated code.</p>

<p>A variation of this strategy is known as refresh-ahead.
The refresh-ahead strategy however is typically focused on performance, rather than fault tolerance, with some implementations trying to predict when a cache item will be requested and refreshing it before it is requested.
Other variations simply refresh all cache items on a schedule.
Both of these implementations may result in more requests, and thus load, to the external service, which may not be desirable.
The refresh-ahead strategy also typically has the cache manage refreshing its own items, whereas strategies like cache-aside still have the application managing refreshing the cache items.</p>

<h3 id="--data-that-is-not-extremely-time-sensitive">- Data that is not extremely time sensitive</h3>

<p>The Stale Cache pattern provides fault tolerance when the external source is unavailable for a period of time.
The greater the expiry time of the data, the more fault tolerance the Stale Cache pattern can provide.</p>

<p>If the data is considered expired after a very short amount of time though, such as a few seconds, then the Stale Cache pattern is not likely to provide much benefit since when there are problems, chances are the external source will be unavailable for more than just a few seconds.</p>

<p>The Stale Cache pattern is highly suited for data that is not extremely time sensitive, and that does not change frequently, as typically data that does not change often can have a longer expiry time.
Examples of this type of data include:</p>

<ul>
  <li>Auth tokens (typically have an expiry of 1 hour or more)</li>
  <li>Product names, descriptions, and images (typically change infrequently, so could mark as stale after 1 hour, but not expire for 48 hours)</li>
  <li>Product prices (if the business values making a sale over selling the item at a slightly out-of-date price)</li>
  <li>Countries, provinces / states, cities, postal / zip codes, etc. (typically change infrequently)</li>
  <li>Lists of clients or users (typically change infrequently)</li>
</ul>

<h2 id="example-implementation-of-the-stale-cache-pattern">Example implementation of the Stale Cache pattern</h2>

<p>Below is a basic example of incorporating the Stale Cache pattern into an in-memory cache that uses the cache-aside strategy in C#.
In-memory cache-aside caches are very popular for client applications, and web services that do not need to horizontally scale greatly.
I build upon the .NET MemoryCache class in this example.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Microsoft.Extensions.Caching.Memory</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.Internal</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.Extensions.Logging</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">Caching</span><span class="p">;</span>

<span class="k">public</span> <span class="k">enum</span> <span class="n">StaleMemoryCacheResult</span>
<span class="p">{</span>
  <span class="n">ValueFound</span><span class="p">,</span>      <span class="c1">// Item is still fresh.</span>
  <span class="n">StaleValueFound</span><span class="p">,</span> <span class="c1">// Item is stale, but still usable.</span>
  <span class="n">ValueNotFound</span>    <span class="c1">// Item is not in cache, so it expired or was never added.</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">StaleMemoryCache</span>
<span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">StaleMemoryCache</span><span class="p">&gt;</span> <span class="n">_logger</span><span class="p">;</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="n">IMemoryCache</span> <span class="n">_memoryCache</span><span class="p">;</span>

  <span class="k">public</span> <span class="nf">StaleMemoryCache</span><span class="p">(</span><span class="n">ILogger</span><span class="p">&lt;</span><span class="n">StaleMemoryCache</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">,</span> <span class="n">IMemoryCache</span> <span class="n">memoryCache</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">logger</span><span class="p">));</span>
    <span class="n">_memoryCache</span> <span class="p">=</span> <span class="n">memoryCache</span> <span class="p">??</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentNullException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">memoryCache</span><span class="p">));</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="n">StaleMemoryCacheResult</span> <span class="n">TryGetValue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">object</span> <span class="n">key</span><span class="p">,</span> <span class="k">out</span> <span class="n">T</span><span class="p">?</span> <span class="k">value</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="kt">bool</span> <span class="n">hasValue</span> <span class="p">=</span> <span class="n">_memoryCache</span><span class="p">.</span><span class="nf">TryGetValue</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="k">out</span> <span class="n">CacheItem</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;?</span> <span class="n">item</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(!</span><span class="n">hasValue</span> <span class="p">||</span> <span class="n">item</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="n">_logger</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"Cache miss for key '{key}'. Item was either never added to cache or it expired."</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
      <span class="k">value</span> <span class="p">=</span> <span class="k">default</span><span class="p">;</span>
      <span class="k">return</span> <span class="n">StaleMemoryCacheResult</span><span class="p">.</span><span class="n">ValueNotFound</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kt">bool</span> <span class="n">isStale</span> <span class="p">=</span> <span class="n">item</span><span class="p">.</span><span class="n">StaleDate</span> <span class="p">&lt;</span> <span class="n">DateTimeOffset</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">isStale</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="n">_logger</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"Cache hit for key '{key}', but item is stale."</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
      <span class="k">value</span> <span class="p">=</span> <span class="n">item</span><span class="p">.</span><span class="n">Value</span><span class="p">;</span>
      <span class="k">return</span> <span class="n">StaleMemoryCacheResult</span><span class="p">.</span><span class="n">StaleValueFound</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">_logger</span><span class="p">.</span><span class="nf">LogDebug</span><span class="p">(</span><span class="s">"Cache hit for key '{key}'."</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
    <span class="k">value</span> <span class="p">=</span> <span class="n">item</span><span class="p">.</span><span class="n">Value</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">StaleMemoryCacheResult</span><span class="p">.</span><span class="n">ValueFound</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">public</span> <span class="k">void</span> <span class="n">Set</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">object</span> <span class="n">key</span><span class="p">,</span> <span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="n">TimeSpan</span> <span class="n">timeUntilItemIsStale</span><span class="p">,</span> <span class="n">TimeSpan</span> <span class="n">timeUntilItemExpires</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="kt">var</span> <span class="n">cacheItem</span> <span class="p">=</span> <span class="k">new</span> <span class="n">CacheItem</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
    <span class="p">{</span>
      <span class="n">Value</span> <span class="p">=</span> <span class="k">value</span><span class="p">,</span>
      <span class="n">StaleDate</span> <span class="p">=</span> <span class="n">DateTimeOffset</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">timeUntilItemIsStale</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="kt">var</span> <span class="n">expiryDate</span> <span class="p">=</span> <span class="n">DateTimeOffset</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">timeUntilItemExpires</span><span class="p">);</span>
    <span class="n">_memoryCache</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">cacheItem</span><span class="p">,</span> <span class="n">expiryDate</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">private</span> <span class="k">class</span> <span class="nc">CacheItem</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
  <span class="p">{</span>
    <span class="k">public</span> <span class="n">T</span> <span class="n">Value</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="k">default</span><span class="p">!;</span>
    <span class="k">public</span> <span class="n">DateTimeOffset</span> <span class="n">StaleDate</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="n">DateTimeOffset</span><span class="p">.</span><span class="n">MinValue</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The main differences between this and your typical time-based expiration cache are:</p>

<ul>
  <li>To <code class="language-plaintext highlighter-rouge">Set</code> an item in the cache you must provide <em>two</em> time values; the time until the item is considered stale, and the time until the item expires (is removed from the cache).
Current cache libraries typically only allow you to specify the expiry time.</li>
  <li>The cache returns a <code class="language-plaintext highlighter-rouge">StaleMemoryCacheResult</code> enum instead of a boolean.
This allows the caller to know if the item was found in the cache, if the item was found but is stale, or if the item was not found in the cache.
There are other ways to implement this, such as having explicit functions for checking if an item is stale or not, or returning a more complex object that contains more information instead of returning an enum.
This choice was just a personal preference.</li>
</ul>

<p>If you are not a fan of the variable names <code class="language-plaintext highlighter-rouge">timeUntilItemIsStale</code> and <code class="language-plaintext highlighter-rouge">timeUntilItemExpires</code>, alternatives could be <code class="language-plaintext highlighter-rouge">minTtl</code> and <code class="language-plaintext highlighter-rouge">maxTtl</code> respectively (TTL = Time To Live).</p>

<p>An example of using the above in-memory cache with the cache-aside strategy might look like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">readonly</span> <span class="n">TimeSpan</span> <span class="n">TimeBeforeFetchingFreshAuthToken</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromMinutes</span><span class="p">(</span><span class="m">30</span><span class="p">);</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">TimeSpan</span> <span class="n">MaxTimeToUseStaleAuthTokenFor</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromMinutes</span><span class="p">(</span><span class="m">120</span><span class="p">);</span>

<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="nf">GetAuthToken</span><span class="p">()</span>
<span class="p">{</span>
  <span class="kt">string</span><span class="p">?</span> <span class="n">authToken</span><span class="p">;</span>
  <span class="kt">var</span> <span class="n">cacheResult</span> <span class="p">=</span> <span class="n">_staleMemoryCache</span><span class="p">.</span><span class="nf">TryGetValue</span><span class="p">(</span><span class="n">AuthTokenCacheKey</span><span class="p">,</span> <span class="k">out</span> <span class="n">authToken</span><span class="p">);</span>

  <span class="c1">// If we have a fresh auth token in the cache, return it.</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">cacheResult</span> <span class="p">==</span> <span class="n">StaleMemoryCacheResult</span><span class="p">.</span><span class="n">ValueFound</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="n">authToken</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// Otherwise, we either don't have an auth token or it is stale, so try to get a fresh one.</span>
  <span class="c1">// (An alternative approach could be to return the stale auth token right away and try to get a fresh one in the background)</span>
  <span class="kt">var</span> <span class="n">authItem</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">GetFreshAuthTokenFromExternalService</span><span class="p">();</span>

  <span class="c1">// If we successfully retrieved a fresh auth token, add it to the cache and return it.</span>
  <span class="kt">bool</span> <span class="n">authRetrievedSuccessfully</span> <span class="p">=</span> <span class="p">(</span><span class="n">authItem</span> <span class="k">is</span> <span class="n">not</span> <span class="k">null</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">authRetrievedSuccessfully</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">authToken</span> <span class="p">=</span> <span class="n">authItem</span><span class="p">.</span><span class="n">Token</span><span class="p">;</span>
    <span class="n">_staleMemoryCache</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="n">AuthTokenCacheKey</span><span class="p">,</span> <span class="n">authToken</span><span class="p">,</span> <span class="n">TimeBeforeFetchingFreshAuthToken</span><span class="p">,</span> <span class="n">MaxTimeToUseStaleAuthTokenFor</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">authToken</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// Otherwise we could not get a fresh auth token, so if we have a stale auth token in the cache, return it.</span>
  <span class="kt">bool</span> <span class="n">weHaveACachedAuthToken</span> <span class="p">=</span> <span class="p">(</span><span class="n">cacheResult</span> <span class="p">==</span> <span class="n">StaleMemoryCacheResult</span><span class="p">.</span><span class="n">StaleValueFound</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">weHaveACachedAuthToken</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">_logger</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"Could not retrieve a fresh auth token, so using a stale cached one instead."</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">authToken</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// Otherwise we could not get a fresh auth token and we do not have one in the cache, so throw an exception.</span>
  <span class="k">throw</span> <span class="k">new</span> <span class="nf">Exception</span><span class="p">(</span><span class="s">"Could not retrieve an auth token and there are no cached ones to use."</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Check out <a href="https://gist.github.com/deadlydog/3169ff35abc95c4607acf5730a12932b">this gist</a> for more complete example code, including unit tests.</p>

<p>In this example you can see that <code class="language-plaintext highlighter-rouge">TimeBeforeFetchingFreshAuthToken</code> is set to 30 minutes, and <code class="language-plaintext highlighter-rouge">MaxTimeToUseStaleAuthTokenFor</code> to 120 minutes.
Let’s say 120 minutes was chosen because that is how long a new auth token is valid for.
With this in place and assuming the application is being used constantly, the auth token will refresh every 30 minutes.</p>

<p>Consider what would happen if the application retrieves an auth token at 1pm, and then the external auth service goes down from 2pm - 3pm.
In a typical cache-aside strategy, a single expiry time must be chosen.
The developer may choose to expire the cache item after 90 minutes, since that’s close to the max lifetime of the auth token.
Or perhaps they ignore the auth token lifetime and simply expire the cache item every 30 minutes.
In both cases the application would end up unable to retrieve a fresh auth token at 2:30pm and suffer an outage.
Using the Stale Cache with the values in the example code above however would allow the application to always have a valid auth token, as it would obtain a new auth token at 2pm which would be good until 4pm.</p>

<p><img src="/assets/Posts/2023-06-27-Increase-system-fault-tolerance-with-the-Stale-Cache-pattern/example-of-traditional-cache-failing-and-stale-cache-succeeding.drawio.png" alt="Example of traditional caches failing and Stale Cache succeeding" /></p>

<p>The above is only one possible implementation of the Stale Cache pattern.
I chose to keep it simple for the sake of this example, but there are many other things you could do to improve it.
You may want to have it fetch fresh items in the background and return stale items immediately.
You may want to store a delegate with the cache item that the <code class="language-plaintext highlighter-rouge">StaleMemoryCache</code> class can call to refresh the cache item, and move the date and <code class="language-plaintext highlighter-rouge">StaleMemoryCacheResult</code> comparison logic of when to retrieve a fresh item into the cache class itself.</p>

<p>While this example shows how to use the Stale Cache pattern with an in-memory cache-aside strategy, it can be similarly incorporated into distributed caches, side-car caches, and other cache strategies as well.</p>

<h2 id="considerations">Considerations</h2>

<ul>
  <li>
    <p>While the Stale Cache pattern can help make your systems more fault tolerant, without proper observability it may also hide external system failures.
Consider adding telemetry on how often stale items are used, in addition to cache hits (fresh) and misses (expired).
This may help identify when external services are down or slow, or when your cache is not working as expected, even though the application works.
It also feels great to see your implementation working as expected and knowing when it saved your system from failing and causing an outage.</p>
  </li>
  <li>
    <p>Adding the Stale Cache pattern to your caches does not mean you should throw away other good caching practices.
For example, if you decide to use a distributed cache, you likely still want a locking mechanism in place to prevent <a href="https://en.wikipedia.org/wiki/Cache_stampede">a cache stampede</a>.</p>
  </li>
  <li>
    <p>As mentioned earlier, consider if you want to block while retrieving fresh items, or retrieve them in the background; accuracy vs speed.</p>
  </li>
  <li>
    <p>While the Stale Cache pattern is more forgiving, you should still give careful consideration to the Stale and Expiry times that you choose for each type of data in the cache.</p>
  </li>
  <li>
    <p>Allowing stale items may not make sense in every scenario, such as when data is only allowed to be cached for a second or two.
For those situations, you can achieve the traditional cache behaviour by setting the Stale and Expiry dates to the same value.</p>
  </li>
  <li>
    <p>In general, the optimal configuration is to use a short Stale time and a long Expiry time.
This will allow your application to be more fault tolerant, while still keeping the data as fresh as possible.
When choosing the Stale duration, you should consider both the impact of stale data on your application, and the performance impact on the external service of frequently retrieving fresh data, and try to find a happy medium.</p>
  </li>
</ul>

<h2 id="is-this-a-new-pattern">Is this a new pattern?</h2>

<p>I thought up this pattern many years ago after one of our services whose data did not change very often had an extended outage and it impacted many other services.
I have used and recommended it many times since.
I think it is a relatively straight-forward (and kind of obvious) solution, but I have not seen it documented anywhere, either by this name, <code class="language-plaintext highlighter-rouge">Stale Cache</code> pattern, or any other.
If you know of it by a different name, please let me know in the comments below.</p>

<p>The only implementation I have seen of it in any caching libraries is the refresh-ahead strategy.
However, it can be applied to other caching strategies as well, such as the cache-aside and read-through strategies, giving the fault tolerance advantages without the complexities of refresh-ahead.
So simply saying that the refresh-ahead strategy is the Stale Cache pattern is not accurate; that is just one implementation of it.</p>

<p><a href="https://datatracker.ietf.org/doc/html/rfc5861">RFC 5861</a> introduced the <code class="language-plaintext highlighter-rouge">stale-while-revalidate</code> and <code class="language-plaintext highlighter-rouge">stale-if-error</code> HTTP Cache Control header directives, which are also implementations of Stale Cache pattern.
Even though the spec was proposed in 2010, many web browsers and web services <a href="https://caniuse.com/?search=stale-while-revalidate">still do not support it</a>.
Some of the products that do support these cache control headers though give good guidance on how and when to use it, such as the <a href="https://developer.fastly.com/learning/concepts/stale/">Fastly docs</a> and <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html">Amazon CloudFront docs</a>, which you may find applicable to your own applications when implementing the Stale Cache pattern.</p>

<p>I was actually quite surprised when I could not find any articles naming this pattern.
Obviously, I am not the first person to think of it, but I was not able to find any other references to it.
Perhaps I was just searching for the wrong terms.
If you know of any other articles describing this general pattern (not just an implementation of it, like refresh-ahead), please let me know in the comments below.</p>

<blockquote>
  <p>Update December 2023: I came across <a href="https://github.com/ZiggyCreatures/FusionCache">the FusionCache .NET library</a> that implements the Stale Cache pattern, calling it the <a href="https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/FailSafe.md"><code class="language-plaintext highlighter-rouge">Fail-Safe</code> mechanism</a>.</p>
</blockquote>

<h2 id="conclusion">Conclusion</h2>

<blockquote>
  <p>There are only two hard things in Computer Science: cache invalidation and naming things.</p>
</blockquote>

<p>While the <code class="language-plaintext highlighter-rouge">Stale Cache</code> pattern does not completely solve cache invalidation, it helps make time-based cache invalidation more forgiving by allowing you to pick a time range instead of a single point in time.</p>

<p>The pattern is not overly complicated or complex, making it fairly easy to understand and implement.
Time-based cache-aside strategies are one of the most common types of caches, and as I’ve shown in this post, extending their functionality to include the Stale Cache pattern is not difficult and can have huge payoffs.</p>

<p>The Stale Cache pattern can make your apps and services more resilient by reducing the amount of time that they depend on external services to be up for.
This is especially important in microservice architectures where the number of external service dependencies are often very high.</p>

<p>The pattern can also help give your apps and services a speed boost by returning stale items immediately instead of blocking while retrieving fresh items from the external source.</p>

<p>I hope this pattern eventually becomes standard functionality in all caching libraries that offer time-based expiration, and that you find it useful in your applications.</p>

<p>Happy caching!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="DotNet" /><category term="CSharp" /><category term="Cache" /><category term="Design patterns" /><category term="Software Development" /><category term="DotNet" /><category term="CSharp" /><category term="Cache" /><category term="Design patterns" /><category term="Software Development" /><summary type="html"><![CDATA[Caching is used to improve the performance of an application. When applied properly, caches can also help increase an application’s fault tolerance, helping to prevent outages and improve the user experience.]]></summary></entry><entry><title type="html">Use Carnac to show key presses on-screen</title><link href="https://blog.danskingdom.com/Use-Carnac-to-show-key-presses-on-screen/" rel="alternate" type="text/html" title="Use Carnac to show key presses on-screen" /><published>2023-04-07T00:00:00+00:00</published><updated>2023-04-07T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Use-Carnac-to-show-key-presses-on-screen</id><content type="html" xml:base="https://blog.danskingdom.com/Use-Carnac-to-show-key-presses-on-screen/"><![CDATA[<p><a href="http://code52.org/carnac/">Carnac</a> is a free, open source, small utility that displays the keys you press on the screen.
This is great for demos, presentations, and screen recordings where you want the viewers to see what keys you are pressing.</p>

<p>Here is a screenshot of Carnac in action.
<img src="/assets/Posts/2023-04-07-Use-Carnac-to-show-key-presses-on-screen/carnac-in-action-screenshot.png" alt="Screenshot of Carnac in action" /></p>

<p>While Carnac is a great tool, it is not perfect.
<a href="https://github.com/Code52/carnac">The official version</a> looks to no longer be maintained since 2020, with many outstanding issues and pull requests.</p>

<p>I personally use <a href="https://github.com/bfritscher/carnac/releases">this fork of Carnac by bfritscher</a> which adds a few features, like the ability to also display mouse clicks.
It too has not been updated since 2020 though.</p>

<p>I have only been able to get Carnac to work on my primary monitor (<a href="https://github.com/Code52/carnac/issues/257#issuecomment-1630738758">see issue here</a>), and have noticed some other things not working as expected while playing with many of the various settings.
Once I got it configured how I like however, I have not had any issues with it.</p>

<p>While Carnac is not perfect, it is still a great free tool that I get a lot of value from.
Several people have asked me in <a href="https://www.youtube.com/deadlydog">my YouTube channel</a> comments what tool I was using to show my key presses on-screen, so I decided to write this post to spread the word about Carnac.</p>

<p>Do you know of any other tools like Carnac?
Perhaps you have a fork of Carnac that you have been maintaining?
Let me know in the comments below.</p>

<p>Happy screen casting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="Windows" /><category term="Screen Capture" /><category term="Productivity" /><category term="Windows" /><category term="Screen Capture" /><summary type="html"><![CDATA[Carnac is a free, open source, small utility that displays the keys you press on the screen. This is great for demos, presentations, and screen recordings where you want the viewers to see what keys you are pressing.]]></summary></entry><entry><title type="html">Change these VS Code default settings to make it even more awesome</title><link href="https://blog.danskingdom.com/Visual-Studio-Code-default-settings-to-change/" rel="alternate" type="text/html" title="Change these VS Code default settings to make it even more awesome" /><published>2023-03-14T00:00:00+00:00</published><updated>2024-01-14T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Visual-Studio-Code-default-settings-to-change</id><content type="html" xml:base="https://blog.danskingdom.com/Visual-Studio-Code-default-settings-to-change/"><![CDATA[<p>This post is a collection of Visual Studio Code settings that I think are worth changing from their default values.
This only includes native VS Code settings; no extensions or themes.</p>

<h2 id="introduction">Introduction</h2>

<p>Visual Studio Code is an amazing editor that has a ton of settings, with more being introduced all the time.
If you don’t keep up with all the release notes, it’s easy to miss some of the new settings.
Looking through the list of settings can be overwhelming and time consuming, and it’s hard to know which ones are worth changing.
Below I list settings that you may want to consider changing from their default values.</p>

<blockquote>
  <p>I understand that most of these settings are personal preference, and you may not agree with all of my suggestions.
I am simply listing these to make you aware of them.
I encourage you to play around with them and see what works best for you.</p>
</blockquote>

<p>I’ll note that some of the settings below are mentioned even though they are on by default.
This is done for features that were not originally turned on by default when they were introduced, and you may still have them turned off in VS Code due to Settings Sync carrying forward the original setting value.</p>

<h3 id="settings-sync">Settings Sync</h3>

<p>Speaking of Settings Sync, if you do not have it enabled, you should consider doing so.
It allows you to sync your settings across multiple machines, and can also sync extensions, keyboard shortcuts, user snippets and tasks, UI state, and profiles.
It is great for when you wipe your computer, or migrate to a new machine, as it brings all your customizations with you.
It used to require an extension, but is now built into VS Code.
Read more about it in <a href="https://code.visualstudio.com/docs/editor/settings-sync">the official docs</a>.</p>

<h3 id="changing-settings-in-vs-code">Changing settings in VS Code</h3>

<p>You can access the settings in VS Code from the <code class="language-plaintext highlighter-rouge">File</code> -&gt; <code class="language-plaintext highlighter-rouge">Preferences</code> -&gt; <code class="language-plaintext highlighter-rouge">Settings</code> menu, or use the keyboard shortcut <kbd>Ctrl</kbd>+<kbd>,</kbd> (<kbd>Cmd</kbd>+<kbd>,</kbd> on macOS).
From there, use the search box to find the settings mentioned below in this post.</p>

<p>If you prefer to modify the <code class="language-plaintext highlighter-rouge">settings.json</code> file directly, you can open it using the button in the top right corner of the settings window.
The settings.json file typically only shows setting values that have been changed from their default.
You can use the GUI setting’s gear icon to <code class="language-plaintext highlighter-rouge">Reset Setting</code> to its default value, and it will be removed from the settings.json file.</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/screenshot-of-settings-screen.png" alt="Screenshot of the Settings UI in VS Code" /></p>

<p>If you want to quickly see what settings you’ve changed from the default values, look in the settings.json file.</p>

<p>While testing out many different settings, I recommend opening the Settings tab in a separate tab group to the side (grab the tab and drag it to the right side of the text editor), or else opening it a separate VS Code instance on another monitor.
This way you can see both the Settings tab and another tab at the same time, allowing you to modify settings and see the results instantly in the other tab without having to switch back and forth.</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/dock-settings-tab-on-the-right-side-of-window.png" alt="Dock settings tab to the right side of the editor" /></p>

<p>Alright, enough preamble, let’s see the settings!</p>

<p>If there are other settings you think belong on this list, please let me know in the comments.</p>

<!-- Related: You may also be interested in this post about [Visual Studio Code extensions to install](/Visual-Studio-Code-extensions-to-install/). -->

<h2 id="editor-ui-settings">Editor UI settings</h2>

<h3 id="show-hidden-characters">Show hidden characters</h3>

<p>Make hidden characters easily visible in the editor.
This is on by default for all languages, except for Plain Text and Markdown, so we have to turn it on there explicitly.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Unicode Highlight: Invisible Characters</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"[markdown]"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"editor.unicodeHighlight.invisibleCharacters"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="nl">"[plaintext]"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"editor.unicodeHighlight.invisibleCharacters"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/show-invisible-characters-settings-result.png" alt="Result of enabling the Show Invisible Characters setting" /></p>

<p><strong>Note:</strong> There are separate settings for the <code class="language-plaintext highlighter-rouge">Markdown</code> and <code class="language-plaintext highlighter-rouge">PlainText</code> languages, which are <code class="language-plaintext highlighter-rouge">false</code> by default, so be sure to enable all of them.</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/show-invisible-characters-multiple-settings.png" alt="Screenshot showing there are multiple language settings for showing invisible characters" /></p>

<h3 id="render-whitespace">Render whitespace</h3>

<p>By default VS Code only shows a glyph for space and tab characters when you highlight them.
I prefer seeing the glyph any time there is potentially unexpected whitespace, which is typically when there is more than one whitespace character, and is what the <code class="language-plaintext highlighter-rouge">boundary</code> setting value does.
This means you will also see glyphs for indentation, but I find it helpful to see the indentation level at a glance (Python/YAML anyone 😏), and to ensure I’m not mixing tabs and spaces (because I’m weird like that ok! 😜).</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Render Whitespace</code> to <code class="language-plaintext highlighter-rouge">boundary</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.renderWhitespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"boundary"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/render-whitespace-setting-result.png" alt="Result of using render whitespace with shown configuration" /></p>

<h3 id="editor-sticky-scroll">Editor sticky scroll</h3>

<p>As you scroll down through nested classes/functions/structures, the names stick to the top of the editor making it easy to see the nested scope you’re currently working in.
Works in many different languages, such as JavaScript, TypeScript, C#, JSON, YAML, Markdown, etc.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Sticky Scroll: Enabled</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.stickyScroll.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/editor-sticky-scroll-setting-result.png" alt="Result of using sticky scroll in YAML" /></p>

<p>And here it is in action while scrolling through a Markdown file:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/editor-sticky-scroll-setting-result-animation.gif" alt="Animation showing sticky scroll in action in Markdown" /></p>

<p>You can also quickly toggle this setting on/off from the <code class="language-plaintext highlighter-rouge">View</code> &gt; <code class="language-plaintext highlighter-rouge">Appearance</code> menu.</p>

<h3 id="breadcrumbs-navigation">Breadcrumbs navigation</h3>

<p>Displays a breadcrumbs navigation bar at the top of the editor for the current tab, showing not only the file path, but also the class and function that is currently in focus.</p>

<p>This setting is now on by default, but it might be turned off if you’ve been using VS Code with Settings Sync for a while.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Breadcrumbs: Enabled</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"breadcrumbs.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/breadcrumbs-navigation-setting-result.png" alt="Result of using breadcrumbs navigation" /></p>

<h3 id="minimap-for-vertical-scrolling">Minimap for vertical scrolling</h3>

<p>The minimap is a small preview of the entire file that is displayed on the right side of the editor, replacing the traditional vertical scrollbar.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Minimap: Enabled</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.minimap.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></code></pre></div></div>

<p>In addition to just enabling the minimap, you can also customize it with many different settings.
If you search for <code class="language-plaintext highlighter-rouge">minimap</code> in the settings, you will see the many different settings that you can configure.
The minimap configuration is a very personal preference, so I recommend trying out different settings to see what works best for you.</p>

<p>Below are the non-default minimap JSON settings that I use:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.minimap.showSlider"</span><span class="p">:</span><span class="w"> </span><span class="s2">"always"</span><span class="err">,</span><span class="w">
</span><span class="nl">"editor.minimap.maxColumn"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="err">,</span><span class="w">
</span><span class="nl">"editor.minimap.renderCharacters"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="err">,</span><span class="w">
</span><span class="nl">"editor.minimap.size"</span><span class="p">:</span><span class="w"> </span><span class="s2">"fill"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/minimap-setting-result.png" alt="Result of using the minimap for vertical scrolling with shown configuration" /></p>

<h3 id="cursor-style">Cursor style</h3>

<p>Change the caret (typing cursor) style.
I actually prefer the default <code class="language-plaintext highlighter-rouge">line</code> style, but included this setting in the list as you may like a different style better.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor: Cursor Style</code> to <code class="language-plaintext highlighter-rouge">line</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.cursorStyle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"line"</span><span class="w">
</span></code></pre></div></div>

<p>Result showing each of the different cursor styles:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/cursor-styles-example-results.png" alt="Result of using the different cursor styles" /></p>

<h3 id="cursor-blinking">Cursor blinking</h3>

<p>Control if the caret (typing cursor) blinks or not, as well as the animation it uses for the blink.
This is a subtle UI tweak that you might not even notice, but I like it.
I personally like the <code class="language-plaintext highlighter-rouge">expand</code> animation, but you can choose whatever you prefer.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor: Cursor Blinking</code> to <code class="language-plaintext highlighter-rouge">expand</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.cursorBlinking"</span><span class="p">:</span><span class="w"> </span><span class="s2">"expand"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p><a href="https://dev.to/chris__sev/animating-your-vs-code-cursor-w-cursor-blinking-1p30">This other blog</a> shows gifs of the different animations that you can choose from.</p>

<h3 id="smooth-scrolling">Smooth scrolling</h3>

<p>Adds a slight animation to vertically scrolling, rather than the page just jumping to the new location.
I find this makes scrolling less jarring and easier to differentiate which direction you are scrolling.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor: Smooth Scrolling</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.smoothScrolling"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="smooth-caret-animation">Smooth caret animation</h3>

<p>This is similar to the <a href="#smooth-scrolling">Smooth scrolling setting</a>, only for the caret (the typing cursor).
This adds a slight animation of the caret moving to the new location, making it easier to follow, rather than it jumping and just appearing at the new location.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor: Cursor Smooth Caret Animation</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.cursorSmoothCaretAnimation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"on"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result of smooth caret animation off:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/smooth-caret-animation-off-result.gif" alt="Result of not using smooth caret animation" /></p>

<p>Result of smooth caret animation on (does not look as smooth and nice in the gif due to its low frame rate):</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/smooth-caret-animation-on-result.gif" alt="Result of using smooth caret animation" /></p>

<h3 id="editor-decoration-colors">Editor decoration colors</h3>

<p>In addition to using glyphs in the tab name to indicate things like the file has been modified, VS Code will also change the color the tab text.</p>

<p>This setting is now on by default, but it might be turned off if you’ve been using VS Code with Settings Sync for a while.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Editor › Decorations: Colors</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.editor.decorations.colors"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/editor-decoration-colors-setting-result.png" alt="Result of enabling editor decoration colors" /></p>

<h3 id="wrap-tabs">Wrap tabs</h3>

<p>When you have many tabs open you can have them wrap and create another row, rather than having a horizontal scrollbar appear and needing to scroll the tabs into view.
This uses more vertical space, but you can see all of your open tabs at once.
This will create multiple rows of tabs if needed.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Editor: Wrap Tabs</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.editor.wrapTabs"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/wrap-tabs-setting-result.png" alt="Screenshot of how many tabs look with wrap tabs enabled" /></p>

<h3 id="tab-height">Tab height</h3>

<p>You can reduce the tab height to save some vertical space, which is especially nice when you have tab wrapping enabled and have multiple rows of tabs.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Window › Density: Editor Tab Height</code> to <code class="language-plaintext highlighter-rouge">compact</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"window.density.editorTabHeight"</span><span class="p">:</span><span class="w"> </span><span class="s2">"compact"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="pinned-tabs-on-separate-row">Pinned tabs on separate row</h3>

<p>You can have pinned tabs be displayed on their own row, above non-pinned tabs.
Tabs can then be easily pinned and unpinned by dragging them in and out of the pinned row.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Editor: Pinned Tabs On Separate Row</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.editor.pinnedTabsOnSeparateRow"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result before:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/pinned-tabs-on-separate-row-off.png" alt="Screenshot of pinned tabs not on a separate row" /></p>

<p>and after:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/pinned-tabs-on-separate-row-on.png" alt="Screenshot of pinned tabs on a separate row" /></p>

<h3 id="pinned-tab-size">Pinned tab size</h3>

<p>Set the tab width for pinned tabs to be smaller.
I find if I’ve pinned a tab, I often know what it is and don’t need to see the full filename in the tab.
You can save some horizontal space by shrinking the tab size for pinned tabs.
This is less of an issue if you have pinned tabs show on their own separate row, but I still like to use it as some files have very long names.</p>

<p>You will still see the full filename in the tooltip when you hover the mouse over the tab, and in the <a href="#breadcrumbs-navigation">breadcrumbs navigation bar</a> when you have the file selected.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Editor: Pinned Tab Sizing</code> to <code class="language-plaintext highlighter-rouge">shrink</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.editor.pinnedTabSizing"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shrink"</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/pinned-tab-size-setting-result.png" alt="Result of using pinned tab sizing with shown configuration" /></p>

<h3 id="preview-editor-tab">Preview editor tab</h3>

<p>When you single click on a file in the file tree, it will open in the preview editor tab by default.
There is only one preview tab, so if you single click on a different file in the file tree, it will close the first file you had open so it can display the new file in the preview tab.
To have the file open in a non-preview tab, you can double click on the file in the file tree, double-click the preview tab, or edit the file.</p>

<p>If you do not like the preview tab, you can disable it and have all files open in non-preview tabs.
I personally like using the preview tab, but I know many people do not.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Editor: Enable Preview</code> to <code class="language-plaintext highlighter-rouge">false</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.editor.enablePreview"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="bracket-pair-colorization">Bracket pair colorization</h3>

<p>Colorize matching brackets/parenthesis, making it easier to see which opening bracket matches which closing bracket.</p>

<p>There used to be several extensions that provided this functionality, but now it is built into VS Code.</p>

<p>This setting is now on by default, but it might be turned off if you’ve been using VS Code with Settings Sync for a while.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Bracket Pair Colorization: Enabled</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.bracketPairColorization.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result (notice how it’s easy to see which opening bracket matches which closing bracket):</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/bracket-pair-colorization-setting-result.png" alt="Screenshot of bracket pair colorization in action" /></p>

<h3 id="bracket-pair-guides">Bracket pair guides</h3>

<p>Add a vertical or horizontal line to the editor to help you see which pair of brackets/parenthesis you are currently modifying code for.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Guides: Bracket Pairs</code> to <code class="language-plaintext highlighter-rouge">active</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.guides.bracketPairs"</span><span class="p">:</span><span class="w"> </span><span class="s2">"active"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/bracket-pair-guides-setting-result.gif" alt="Gif of bracket pair guides in action" /></p>

<h3 id="editor-font">Editor font</h3>

<p>Change the font used in the editor.
This is a very personal preference, and may require you to install the font on your machine.</p>

<p>I personally like the <code class="language-plaintext highlighter-rouge">CaskaydiaCove Nerd Font</code> font.
Another popular one is <code class="language-plaintext highlighter-rouge">FiraCode</code>.
Both of these fonts support ligatures, and can be downloaded for free from <a href="https://www.nerdfonts.com/font-downloads">Nerd Fonts</a>.</p>

<p>You can specify multiple fonts, and VS Code will use the first one that is available on your machine.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor: Font Family</code> to <code class="language-plaintext highlighter-rouge">'CaskaydiaCove Nerd Font Mono',Consolas, 'Courier New', monospace</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.fontFamily"</span><span class="p">:</span><span class="w"> </span><span class="s2">"'CaskaydiaCove Nerd Font Mono',Consolas, 'Courier New', monospace"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="editor-font-ligatures">Editor font ligatures</h3>

<p>If your <a href="#editor-font">editor font</a> supports it, you can enable font ligatures.
Font ligatures are a way to combine multiple characters into a single glyph, such as <code class="language-plaintext highlighter-rouge">==</code> becoming <code class="language-plaintext highlighter-rouge">≡</code>, or <code class="language-plaintext highlighter-rouge">-&gt;</code> becoming <code class="language-plaintext highlighter-rouge">→</code>.</p>

<p>I personally do not like font ligatures, but I know many people do.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor: Font Ligatures</code>, which must be edited in the settings.json file.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.fontLigatures"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="file-tree-indentation">File tree indentation</h3>

<p>Increase the indentation of nested items in the file tree so it is easier to tell when files are in a folder.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Tree: Indent</code> to <code class="language-plaintext highlighter-rouge">11</code>, or whatever value you prefer.
Default is 8.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.tree.indent"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="file-tree-indentation-guide-lines">File tree indentation guide lines</h3>

<p>Show vertical lines in the file tree to help you see which files are in which folders.
By default these are only shown when you hover over the mouse over the file tree, but I prefer to always show them.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Tree: Render Indent Guides</code> to <code class="language-plaintext highlighter-rouge">always</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.tree.renderIndentGuides"</span><span class="p">:</span><span class="w"> </span><span class="s2">"always"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result (before and after):</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/always-show-workbench-tree-indent-guides-before-and-after-result.png" alt="Before and after screenshot of always showing the indent guides" /></p>

<h3 id="file-tree-indentation-guide-lines-color">File tree indentation guide lines color</h3>

<p>Change the color of the file tree indentation guide lines so they are easier to see.
I found the default color to be too dark and hard to see on some monitors, so I make it brighter.
You can choose whatever color you prefer.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench: Color Customizations</code>, which must be edited in the settings.json file.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.colorCustomizations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"tree.indentGuidesStroke"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#999999"</span><span class="w">  </span><span class="err">//</span><span class="w"> </span><span class="err">Default</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">#</span><span class="mi">555555</span><span class="err">.</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="file-tree-sticky-scroll">File tree sticky scroll</h3>

<p>You can also enable sticky scroll in the file navigation tree.
This will keep the directory hierarchy visible as you scroll through the file tree.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Tree: Enable Sticky Scroll</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.tree.enableStickyScroll"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/file-tree-sticky-scroll-setting-result.png" alt="Result of using sticky scroll in the file navigation tree" /></p>

<h3 id="file-tree-compact-folders">File tree compact folders</h3>

<p>By default the file navigation tree will collapse folders that only contain a single folder.
This saves a bit of vertical space, but can also make it harder to drag and drop files or folders into the collapsed folder.
I prefer to disable it.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Explorer: Compact Folders</code> to <code class="language-plaintext highlighter-rouge">false</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"explorer.compactFolders"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result (enabled and disabled):</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/file-tree-explorer-compact-folders-setting-result.png" alt="Result of disabling compact folders in the file navigation tree" /></p>

<h3 id="debug-toolbar-location">Debug toolbar location</h3>

<p>By default the debug toolbar floats so that it is always visible and can be repositioned.
The problem is it often floats over other controls or tabs, making them hard to access.
You can dock the debug toolbar, but then it is only visible when you are in the Run And Debug view.
Instead, I prefer to show the debug toolbar in the Command Center.
This way it is always visible and accessible, but does not block any other controls.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Debug: Tool Bar Location</code> to <code class="language-plaintext highlighter-rouge">commandCenter</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"debug.toolBarLocation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"commandCenter"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result before, with controls obstructed:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/debug-toolbar-location-default-floating.png" alt="Screenshot of debug toolbar floating over controls" /></p>

<p>Result after:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/debug-toolbar-location-command-center-result.png" alt="Screenshot of debug toolbar in command center" /></p>

<h3 id="side-bar-location">Side bar location</h3>

<p>By default the side bar is shown on the left side of the editor.
I personally prefer this, but others prefer it on the right side so that when you open and close the side bar, all the text in the editor does not suddenly get shifted.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Side Bar: Location</code> to <code class="language-plaintext highlighter-rouge">right</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.sideBar.location"</span><span class="p">:</span><span class="w"> </span><span class="s2">"right"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result of side bar on the left (default):</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/side-bar-location-left-result.png" alt="Screenshot of side bar on the left" /></p>

<p>Result of side bar on the right:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/side-bar-location-right-result.png" alt="Screenshot of side bar on the right" /></p>

<h3 id="activity-bar-location">Activity bar location</h3>

<p>By default the activity bar is shown on the side of the Side Bar.
If you want to save a bit of horizontal space at the expense of being able to see more extension icons at once, you can move the activity bar to the top of the Side Bar.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Workbench › Activity Bar: Location</code> to <code class="language-plaintext highlighter-rouge">top</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"workbench.activityBar.location"</span><span class="p">:</span><span class="w"> </span><span class="s2">"top"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Result:</p>

<p><img src="/assets/Posts/2023-03-14-Visual-Studio-Code-default-settings-to-change/activity-bar-location-top-result.png" alt="Screenshot of activity bar on the top" /></p>

<h2 id="editor-file-and-formatting-settings">Editor file and formatting settings</h2>

<h3 id="save-files-automatically">Save files automatically</h3>

<p>If you’re tired of constantly hitting <kbd>Ctrl</kbd>+<kbd>S</kbd> to save your files, you can enable auto-save.</p>

<p>I personally like having it save files automatically after 1 second, but you can also choose to have it save when the editor or VS Code lose focus.
Choose whatever works best for you.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Files: Auto Save</code> to <code class="language-plaintext highlighter-rouge">afterDelay</code>, and <code class="language-plaintext highlighter-rouge">Files: Auto Save Delay</code> to <code class="language-plaintext highlighter-rouge">1000</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"files.autoSave"</span><span class="p">:</span><span class="w"> </span><span class="s2">"afterDelay"</span><span class="err">,</span><span class="w">
</span><span class="nl">"files.autoSaveDelay"</span><span class="p">:</span><span class="w"> </span><span class="mi">1000</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="trim-trailing-whitespace">Trim trailing whitespace</h3>

<p>Automatically remove trailing whitespace at the end of any lines when the file is saved.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Files: Trim Trailing Whitespace</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"files.trimTrailingWhitespace"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p><strong>Note:</strong> When working in source controlled files, this can sometimes make the file diff very large if the diff viewer is not configured to ignore whitespace changes.</p>

<h3 id="insert-final-newline">Insert final newline</h3>

<p>Automatically add a final newline to the end of the file when you save it.
This is especially helpful if you are working with text files that will be accessed on unix, as <a href="https://unix.stackexchange.com/a/18789/351983">unix requires a final newline to be present on text files</a>.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Files: Insert Final Newline</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"files.insertFinalNewline"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="trim-final-newlines">Trim final newlines</h3>

<p>While we typically want a final newline, having more than one final newline looks sloppy.
This setting will automatically trim any additional final newlines from the end of the file when you save it.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Files: Trim Final Newlines</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"files.trimFinalNewlines"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="format-on-paste">Format on paste</h3>

<p>When you paste text into the editor, VS Code can automatically format the text for you according to any linters or formatting rules you have configured.
This is especially useful when pasting code snippets from the internet.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Format On Paste</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.formatOnPaste"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="format-on-save-and-format-save-mode">Format on save and format save mode</h3>

<p>Automatically format a file when you save it, applying linting rules and other formatting rules.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Format On Save</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.formatOnSave"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>Along with this setting, you can specify the format mode.
By default it will format the entire file.
I prefer to only format the lines that I have changed, which is what the <code class="language-plaintext highlighter-rouge">modifications</code> setting value does.
This is especially helpful when working with files in source control and you do not want your pull request diff to include a ton of formatting changes for lines that you did not edit, which may hide the actual changes you made.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Format On Save Mode</code> to <code class="language-plaintext highlighter-rouge">modifications</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.formatOnSaveMode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"modifications"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="auto-guess-encoding">Auto guess encoding</h3>

<p>When you open a file, VS Code will guess the encoding of the file so it displays properly, and use that encoding when saving the file, rather than changing it to the default encoding type (which is specified by the <code class="language-plaintext highlighter-rouge">files.encoding</code> setting).</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Files: Auto Guess Encoding</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"files.autoGuessEncoding"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="default-language-for-new-files">Default language for new files</h3>

<p>When you create a new tab in VS Code, but have not saved it and given it a file extension yet, VS Code does not know the file type and what language it is and thus can not provide services like syntax highlighting, intellisense, etc.
You can provide a default language for new tabs that are not saved yet to get these services.</p>

<p>I work a lot in PowerShell, so I set the default language to <code class="language-plaintext highlighter-rouge">powershell</code>.
You may want to set it to something like <code class="language-plaintext highlighter-rouge">javascript</code>, <code class="language-plaintext highlighter-rouge">typescript</code>, <code class="language-plaintext highlighter-rouge">python</code>, <code class="language-plaintext highlighter-rouge">csharp</code>, <code class="language-plaintext highlighter-rouge">markdown</code>, etc.
The supported language identifiers are <a href="https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers">listed here</a>.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Files: Default Language</code> to <code class="language-plaintext highlighter-rouge">powershell</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"files.defaultLanguage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"powershell"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="automatically-use-find-in-selection-if-multiple-lines-are-selected">Automatically use find-in-selection if multiple lines are selected</h3>

<p>If you select multiple lines of text and use <kbd>Ctrl</kbd>+<kbd>F</kbd> or <kbd>Ctrl</kbd>+<kbd>H</kbd> to open the Find or Replace dialog respectively, VS Code can automatically toggle on the <code class="language-plaintext highlighter-rouge">Find in Selection</code> option of the dialog.
To me, it feels intuitive that if I have multiple lines of text selected, I want to search within those lines, not the entire file.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Editor › Find: Auto Find In Selection</code> to <code class="language-plaintext highlighter-rouge">multiline</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"editor.find.autoFindInSelection"</span><span class="p">:</span><span class="w"> </span><span class="s2">"multiline"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h2 id="terminal-settings">Terminal settings</h2>

<h3 id="change-the-default-integrated-terminal-and-add-other-terminals">Change the default integrated terminal, and add other terminals</h3>

<p>If you use the integrated terminal, you can configure the default terminal to be something other than <code class="language-plaintext highlighter-rouge">cmd.exe</code> on Windows or <code class="language-plaintext highlighter-rouge">bash</code> on Linux/macOS.
I prefer PowerShell, so I set it as my default.</p>

<p>I use Windows, so I edit the <code class="language-plaintext highlighter-rouge">Windows</code> profile settings.
Use the <code class="language-plaintext highlighter-rouge">Linux</code> and <code class="language-plaintext highlighter-rouge">Osx</code> profiles accordingly for those platforms.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Terminal › Integrated › Default Profile: Windows</code> to <code class="language-plaintext highlighter-rouge">PowerShell</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"terminal.integrated.defaultProfile.windows"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PowerShell"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>If the terminal that you want to use is not in the list, you can add it to your list of terminals that are available using the setting below.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Terminal › Integrated › Profiles: Windows</code>, which must be edited in the settings.json file.</p>

<p>JSON setting (example):</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"terminal.integrated.profiles.windows"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"PowerShell"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PowerShell"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"icon"</span><span class="p">:</span><span class="w"> </span><span class="s2">"terminal-powershell"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"Command Prompt"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"path"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"${env:windir}</span><span class="se">\\</span><span class="s2">Sysnative</span><span class="se">\\</span><span class="s2">cmd.exe"</span><span class="p">,</span><span class="w">
      </span><span class="s2">"${env:windir}</span><span class="se">\\</span><span class="s2">System32</span><span class="se">\\</span><span class="s2">cmd.exe"</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"icon"</span><span class="p">:</span><span class="w"> </span><span class="s2">"terminal-cmd"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"Git Bash"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Git Bash"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="terminal-font">Terminal font</h3>

<p>Similar to the <a href="#editor-font">editor font</a>, you can configure the font that is used in the integrated terminal.</p>

<p>Again, I prefer the <code class="language-plaintext highlighter-rouge">CaskaydiaCove Nerd Font Mono</code> font which you can get free from <a href="https://www.nerdfonts.com/font-downloads">Nerd Fonts</a>, but feel free to use whatever font you prefer.
You may want to ensure you pick a font that supports any ligatures that you may want to use.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Terminal › Integrated: Font Family</code> to <code class="language-plaintext highlighter-rouge">"CaskaydiaCove Nerd Font Mono"</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"terminal.integrated.fontFamily"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CaskaydiaCove Nerd Font Mono"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="terminal-cursor-style">Terminal cursor style</h3>

<p>You can configure the cursor style that is used in the integrated terminal.
I prefer the <code class="language-plaintext highlighter-rouge">line</code> cursor style over <code class="language-plaintext highlighter-rouge">block</code> or <code class="language-plaintext highlighter-rouge">underline</code>, but you can choose whatever you prefer.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Terminal › Integrated: Cursor Style</code> to <code class="language-plaintext highlighter-rouge">line</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"terminal.integrated.cursorStyle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"line"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="scrollback">Scrollback</h3>

<p>The integrated terminal has a scrollback buffer that stores the output of previous commands.
It defaults to <code class="language-plaintext highlighter-rouge">1000</code> lines, but I prefer to increase it as some scripts can output a lot of text and I like to ensure I’m able to view it all.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Terminal › Integrated: Scrollback</code> to <code class="language-plaintext highlighter-rouge">10000</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"terminal.integrated.scrollback"</span><span class="p">:</span><span class="w"> </span><span class="mi">10000</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h2 id="git-settings">Git settings</h2>

<h3 id="autofetch">Autofetch</h3>

<p>Automatically fetch from the remote repository periodically.
This allows you to see easily if your local repository is out of date or not, without having to fetch manually.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Git: Autofetch</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"git.autofetch"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="auto-stash">Auto stash</h3>

<p>Automatically stash your changes before pulling, and restore them after the pull succeeds.
Without this, you may be presented with a prompt or error if you have uncommitted changes and try to pull.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Git: Auto Stash</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"git.autoStash"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="merge-editor">Merge editor</h3>

<p>When you are resolving merge conflicts, VS Code will open the visual merge editor to help you resolve the conflicting lines.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Git: Merge Editor</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"git.mergeEditor"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="smart-commit">Smart commit</h3>

<p>By default if you have not staged any changes, VS Code will prompt you to stage them before you can commit, giving you the options “Yes”, “No”, “Always”, and “Never”.</p>

<ul>
  <li>If you selected “Always”, it will have automatically stage all changes for you before committing them.</li>
  <li>If you selected “Never”, the Commit button will be disabled until you stage some changes.</li>
</ul>

<p>I prefer to not be prompted and for it to automatically stage all changes before committing, so I use this setting.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Git: Enable Smart Commit</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"git.enableSmartCommit"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<p>If you prefer requiring changes to be manually staged before they can be committed, and do not want to be prompted, you can set the above <code class="language-plaintext highlighter-rouge">enableSmartCommit</code> setting to <code class="language-plaintext highlighter-rouge">false</code>, and also set <code class="language-plaintext highlighter-rouge">suggestSmartCommit</code> to false.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Git: Suggest Smart Commit</code> to <code class="language-plaintext highlighter-rouge">false</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"git.suggestSmartCommit"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h2 id="markdown-settings">Markdown settings</h2>

<h3 id="pasting-urls-into-markdown">Pasting URLs into Markdown</h3>

<p>Automatically wrap hyperlinks pasted into markdown files with the markdown link syntax.</p>

<p>For example, pasting <code class="language-plaintext highlighter-rouge">https://someurl.com</code> into a markdown file will be converted to <code class="language-plaintext highlighter-rouge">[Title](https://someurl.com)</code>.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Markdown › Editor › Paste URL As Formatted Link: Enabled</code> to <code class="language-plaintext highlighter-rouge">smart</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"markdown.editor.pasteUrlAsFormattedLink.enabled"</span><span class="p">:</span><span class="w"> </span><span class="s2">"smart"</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h3 id="markdown-validation">Markdown validation</h3>

<p>Validate your markdown files and notify you of some common errors, such as broken links to pages or images in your repo.</p>

<p>GUI setting: <code class="language-plaintext highlighter-rouge">Markdown › Validation: Enabled</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>JSON setting:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"markdown.validate.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>I hope you’ve found these default setting adjustments useful.</p>

<p>If you have suggestions for other default settings that I missed, please let me know in the comments below.</p>

<p>I plan to update this post as I find more useful settings to tweak, and as new ones are introduced, so be sure to check back from time to time.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio Code" /><category term="Editor" /><category term="Productivity" /><category term="Visual Studio Code" /><category term="VS Code" /><category term="Editor" /><category term="Productivity" /><summary type="html"><![CDATA[This post is a collection of Visual Studio Code settings that I think are worth changing from their default values. This only includes native VS Code settings; no extensions or themes.]]></summary></entry><entry><title type="html">Prevent Admin apps from blocking AutoHotkey by always using UI Access</title><link href="https://blog.danskingdom.com/Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/" rel="alternate" type="text/html" title="Prevent Admin apps from blocking AutoHotkey by always using UI Access" /><published>2023-03-04T00:00:00+00:00</published><updated>2023-03-04T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access</id><content type="html" xml:base="https://blog.danskingdom.com/Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/"><![CDATA[<p>When an app running as Admin has focus, you may not be able to trigger your AutoHotkey (AHK) scripts, hotkeys, and hotstrings.
This is a security feature of Windows where non-elevated apps cannot interact with elevated apps.</p>

<p>This can be frustrating and I have <a href="https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin/">blogged about other ways</a> to get around this issue in the past, but the method described in this post is now my preferred approach.</p>

<p><a href="https://www.autohotkey.com/docs/v1/FAQ.htm#uac">The AutoHotkey documentation</a> explains the issue in more detail, as well as common ways to work around it.
The most common workaround is to <a href="https://www.autohotkey.com/docs/v1/Program.htm#Installer_uiAccess">run your AHK script with UI Access</a>.
By default, the AutoHotkey installer will add a <code class="language-plaintext highlighter-rouge">Run with UI Access</code> context menu item to AHK scripts.
If you use this context menu item to run your script (by right-clicking the AHK script and choosing <code class="language-plaintext highlighter-rouge">Run with UI Access</code>), it will run with UI Access and you will be able to trigger your AHK scripts when an Admin app has focus.</p>

<p><img src="/assets/Posts/2023-03-04-Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/run-ahk-script-with-ui-access.png" alt="Run AHK script with UI Access" /></p>

<p>This solution addresses the issue, but it is not ideal for a couple reasons:</p>

<ol>
  <li>You need to remember to launch your scripts in this special way using the context-menu.
I typically just want to double click a script, or use the Enter key to run it.</li>
  <li>It makes launching AHK scripts from the command line and other applications more difficult and less portable, since you need to use the full path of the <code class="language-plaintext highlighter-rouge">AutoHotkeyU64_UIA.exe</code> executable to launch your AHK scripts, rather than simply letting Windows run it using the default application.
e.g. Using the <code class="language-plaintext highlighter-rouge">Run</code> command in AutoHotkey, <code class="language-plaintext highlighter-rouge">Start-Process</code> in PowerShell, <code class="language-plaintext highlighter-rouge">start</code> in CMD, etc.</li>
</ol>

<h2 id="always-use-ui-access-when-running-ahk-scripts">Always use UI Access when running AHK scripts</h2>

<p>To work around this inconvenience, we can simply change the default executable that is used to run AHK scripts so that they always run with UI Access.
The executable file path we want to update is stored in the following registry key:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HKEY_CLASSES_ROOT\AutoHotkeyScript\Shell\Open\Command
</code></pre></div></div>

<p>The default registry value typically looks like this:</p>

<p><img src="/assets/Posts/2023-03-04-Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/default-autohotkey-open-command-registry-value.png" alt="Default AHK open command registry value" /></p>

<p><strong>NOTE:</strong> Be aware that updating or reinstalling AutoHotkey will restore the registry value to its default value, so you will need to perform the action below again after updating AutoHotkey.</p>

<h3 id="automatically-update-the-registry-value">Automatically update the registry value</h3>

<p>You can use the following AutoHotkey script to automatically update the registry value:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">;</span> <span class="n">Ensure</span> <span class="n">the</span> <span class="n">script</span> <span class="k">is</span> <span class="n">running</span> <span class="k">as</span> <span class="n">Admin</span><span class="p">,</span> <span class="k">as</span> <span class="n">it</span> <span class="n">must</span> <span class="n">be</span> <span class="n">admin</span> <span class="n">to</span> <span class="n">modify</span> <span class="n">the</span> <span class="n">registry</span><span class="p">.</span>
<span class="n">full_command_line</span> <span class="p">:=</span> <span class="nf">DllCall</span><span class="p">(</span><span class="s">"GetCommandLine"</span><span class="p">,</span> <span class="s">"str"</span><span class="p">)</span>
<span class="k">if</span> <span class="nf">not</span> <span class="p">(</span><span class="n">A_IsAdmin</span> <span class="n">or</span> <span class="nf">RegExMatch</span><span class="p">(</span><span class="n">full_command_line</span><span class="p">,</span> <span class="s">" /restart(?!\S)"</span><span class="p">))</span>
<span class="p">{</span>
  <span class="k">try</span>
  <span class="p">{</span>
    <span class="k">if</span> <span class="n">A_IsCompiled</span>
      <span class="n">Run</span> <span class="p">*</span><span class="n">RunAs</span> <span class="s">"%A_ScriptFullPath%"</span> <span class="p">/</span><span class="n">restart</span>
    <span class="k">else</span>
      <span class="n">Run</span> <span class="p">*</span><span class="n">RunAs</span> <span class="s">"%A_AhkPath%"</span> <span class="p">/</span><span class="n">restart</span> <span class="s">"%A_ScriptFullPath%"</span>
  <span class="p">}</span>
  <span class="n">ExitApp</span>
<span class="p">}</span>

<span class="p">;</span> <span class="n">Use</span> <span class="n">the</span> <span class="n">UI</span> <span class="n">Access</span> <span class="n">executable</span> <span class="k">as</span> <span class="n">the</span> <span class="k">default</span> <span class="n">executable</span> <span class="n">to</span> <span class="n">run</span> <span class="n">AHK</span> <span class="n">scripts</span><span class="p">.</span>
<span class="p">;</span> <span class="n">This</span> <span class="n">copies</span> <span class="n">the</span> <span class="n">command</span> <span class="k">value</span> <span class="n">used</span> <span class="n">to</span> <span class="n">run</span> <span class="n">AHK</span> <span class="n">scripts</span> <span class="n">with</span> <span class="n">UI</span> <span class="n">Access</span> <span class="n">to</span> <span class="n">the</span> <span class="k">default</span> <span class="n">Open</span> <span class="n">command</span> <span class="k">value</span><span class="p">.</span>
<span class="n">RegRead</span><span class="p">,</span> <span class="n">UiAccessCommandKeyValue</span><span class="p">,</span> <span class="n">HKEY_CLASSES_ROOT</span><span class="err">\</span><span class="n">AutoHotkeyScript</span><span class="err">\</span><span class="n">Shell</span><span class="err">\</span><span class="n">uiAccess</span><span class="err">\</span><span class="n">Command</span>
<span class="n">RegWrite</span><span class="p">,</span> <span class="n">REG_SZ</span><span class="p">,</span> <span class="n">HKEY_CLASSES_ROOT</span><span class="err">\</span><span class="n">AutoHotkeyScript</span><span class="err">\</span><span class="n">Shell</span><span class="err">\</span><span class="n">Open</span><span class="err">\</span><span class="n">Command</span><span class="p">,,</span> <span class="p">%</span><span class="n">UiAccessCommandKeyValue</span><span class="p">%</span>
</code></pre></div></div>

<p>You can <a href="/assets/Posts/2023-03-04-Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/Always-start-AutoHotkey-with-UI-Access.ahk">download the script here</a>.</p>

<p>Simply run the AHK script and it will update the registry value for you.</p>

<p>You may want to keep that script handy somewhere on your hard drive, as you will need to run it again if you ever update AutoHotkey.</p>

<h3 id="manually-update-the-registry-value">Manually update the registry value</h3>

<p>If you prefer to manually modify the registry instead of using the script above, do the following to update the default executable file path in the registry:</p>

<ol>
  <li>Press <code class="language-plaintext highlighter-rouge">Windows Key</code>+<code class="language-plaintext highlighter-rouge">R</code> to access the Run dialog.</li>
  <li>Type <code class="language-plaintext highlighter-rouge">regedit</code> and hit OK to open the Registry Editor.</li>
  <li>Navigate to <code class="language-plaintext highlighter-rouge">HKEY_CLASSES_ROOT\AutoHotkeyScript\Shell\uiAccess\Command</code>.</li>
  <li>
    <p>Double-click the <code class="language-plaintext highlighter-rouge">(Default)</code> value to edit it, copy its value data, then click Cancel.</p>

    <p><img src="/assets/Posts/2023-03-04-Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/default-autohotkey-uiaccess-command-registry-value.png" alt="Copy the UI Access registry value data" /></p>
  </li>
  <li>Navigate to <code class="language-plaintext highlighter-rouge">HKEY_CLASSES_ROOT\AutoHotkeyScript\Shell\Open\Command</code>.</li>
  <li>
    <p>Double-click the <code class="language-plaintext highlighter-rouge">(Default)</code> value to edit it, paste the value data you copied from the <code class="language-plaintext highlighter-rouge">uiAccess</code> key, then click OK.</p>

    <p><img src="/assets/Posts/2023-03-04-Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/updated-autohotkey-open-command-registry-value.png" alt="Modify the Open registry value data" /></p>
  </li>
</ol>

<p>Now all of your AHK scripts will run with UI Access by default, and you won’t have to worry about launching them in a special way.</p>

<h2 id="caveats-to-running-autohotkey-scripts-with-ui-access">Caveats to running AutoHotkey scripts with UI Access</h2>

<p><a href="https://www.autohotkey.com/docs/v1/Program.htm#Installer_uiAccess">The AutoHotkey documentation</a> points out the following limitations of running AutoHotkey scripts with UI Access:</p>

<blockquote>
  <ul>
    <li>UIA is only effective if the file is in a trusted location; i.e. a Program Files sub-directory.</li>
    <li>UIA.exe files created on one computer cannot run on other computers without first installing the digital certificate which was used to sign them.</li>
    <li>UIA.exe files cannot be started via CreateProcess due to security restrictions. ShellExecute can be used instead. Run tries both.</li>
    <li>UIA.exe files cannot be modified, as it would invalidate the file’s digital signature.</li>
    <li>Because UIA programs run at a different “integrity level” than other programs, they can only access objects registered by other UIA programs. For example, ComObjActive(“Word.Application”) will fail because Word is not marked for UI Access.</li>
    <li>The script’s own windows can’t be automated by non-UIA programs/scripts for security reasons.</li>
    <li>Running a non-UIA script which uses a mouse hook (even as simple as #InstallMouseHook) may prevent all mouse hotkeys from working when the mouse is pointing at a window owned by a UIA script, even hotkeys implemented by the UIA script itself. A workaround is to ensure UIA scripts are loaded last.</li>
    <li>UIA prevents the Gui +Parent option from working on an existing window if the new parent is always-on-top and the child window is not.</li>
  </ul>
</blockquote>

<p>I have not run into any issues, but if one of these limitations affects you then you may not want to always run your AHK scripts with UI Access.</p>

<p>To return AutoHotkey to its original behavior, reset the registry key back to its original value, or simply reinstall/repair AutoHotkey using <a href="https://www.autohotkey.com">the installer</a>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I was getting frustrated that I was not able to use <a href="https://github.com/deadlydog/AHKCommandPicker">AHK Command Picker</a> when a windows running as Admin was in focus.</p>

<blockquote>
  <p>Shameless Plug: <a href="https://github.com/deadlydog/AHKCommandPicker">AHK Command Picker</a> is my free open-source project that allows you to use a GUI picker instead of having to remember a ton of keyboard shortcuts for your AutoHotkey scripts and hotkeys.
It is essentially a quick launcher for any AutoHotkey code.
Check it out if you are interested!</p>
</blockquote>

<p>Running AHK with UI Access by default has fixed the issue for me, and I hope this post helps you too.</p>

<p>Happy scripting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="Productivity" /><category term="Editor" /><category term="AutoHotkey" /><category term="Productivity" /><category term="Editor" /><category term="AHK" /><summary type="html"><![CDATA[When an app running as Admin has focus, you may not be able to trigger your AutoHotkey (AHK) scripts, hotkeys, and hotstrings. This is a security feature of Windows where non-elevated apps cannot interact with elevated apps.]]></summary></entry><entry><title type="html">Change the default AutoHotkey script editor</title><link href="https://blog.danskingdom.com/Change-the-default-AutoHotkey-script-editor/" rel="alternate" type="text/html" title="Change the default AutoHotkey script editor" /><published>2023-03-02T00:00:00+00:00</published><updated>2023-03-06T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Set-default-AutoHotkey-script-editor</id><content type="html" xml:base="https://blog.danskingdom.com/Change-the-default-AutoHotkey-script-editor/"><![CDATA[<p>The Windows context menu provides an <code class="language-plaintext highlighter-rouge">Edit Script</code> option for AutoHotkey <code class="language-plaintext highlighter-rouge">.ahk</code> files.
Unfortunately, it defaults to opening in Notepad, which is not a great editor for AutoHotkey scripts.</p>

<p><img src="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/windows-file-explorer-context-menu-for-ahk-files-to-edit-script.png" alt="Windows File Explorer context menu for .ahk files Edit Script option" /></p>

<p>The same is true when using the <code class="language-plaintext highlighter-rouge">Edit This Script</code> option from the context menu of the tray icon for a running script.</p>

<p><img src="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/autohotkey-tray-icon-context-menu-edit-script.png" alt="A running AutoHotkey scripts tray icon context menu" /></p>

<p>Fortunately there is a simple way to change the default editor for AutoHotkey scripts.</p>

<h2 id="set-the-default-editor-for-autohotkey-scripts-in-the-registry">Set the default editor for AutoHotkey scripts in the Registry</h2>

<p>I came across <a href="https://www.autohotkey.com/board/topic/897-how-to-change-autohotkey-default-editor/">this forum post</a> and <a href="https://www.autohotkey.com/board/topic/23889-how-to-edit-this-script-in-any-editor-other-than/">this one</a>, which led me to the solution of simply modifying the following registry key:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HKEY_CLASSES_ROOT\AutoHotkeyScript\Shell\Edit\Command
</code></pre></div></div>

<p>Further below, I provide some download files to easily modify this registry key for some popular editors.
You can use those, but let’s first look at how to manually modify the registry key.</p>

<h3 id="manually-update-the-registry-value">Manually update the registry value</h3>

<p>To manually modify the registry key value:</p>

<ol>
  <li>Press <code class="language-plaintext highlighter-rouge">Windows Key</code>+<code class="language-plaintext highlighter-rouge">R</code> to access the Run dialog.</li>
  <li>Type <code class="language-plaintext highlighter-rouge">regedit</code> and hit OK to open the Registry Editor.</li>
  <li>Navigate to <code class="language-plaintext highlighter-rouge">HKEY_CLASSES_ROOT\AutoHotkeyScript\Shell\Edit\Command</code>.</li>
  <li>Double-click the <code class="language-plaintext highlighter-rouge">(Default)</code> value to edit it, and update the exe path.</li>
</ol>

<p>By default, it will have a value like <code class="language-plaintext highlighter-rouge">notepad.exe "%1"</code>.</p>

<p>You will want to replace the path to the Notepad executable with the path to your preferred editor’s executable, and surround the file path with double quotes.</p>

<p>I use Visual Studio Code, so this is what my registry entry looks like after updating it:</p>

<p><img src="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/autohotkey-default-script-editor-registry-key-to-edit.png" alt="Setting the registry entry for Visual Studio Code" /></p>

<h4 id="creating-the-registry-key-if-it-does-not-exist">Creating the registry key if it does not exist</h4>

<p>If the registry key does not exist, we can <a href="https://stackoverflow.com/a/45914527/602585">create it manually</a> by doing the following in the Registry Editor:</p>

<ol>
  <li>Navigate to <code class="language-plaintext highlighter-rouge">HKEY_CLASSES_ROOT\AutoHotkeyScript\Shell</code> in the Registry Editor.</li>
  <li>Right-click the <code class="language-plaintext highlighter-rouge">Shell</code> folder, select <code class="language-plaintext highlighter-rouge">New</code> &gt; <code class="language-plaintext highlighter-rouge">Key</code> and name this <code class="language-plaintext highlighter-rouge">Edit</code>.</li>
  <li>Double-click the <code class="language-plaintext highlighter-rouge">(Default)</code> entry in the new <code class="language-plaintext highlighter-rouge">Edit</code> folder, and set its value to <code class="language-plaintext highlighter-rouge">Edit Script</code>.</li>
  <li>Right-click the <code class="language-plaintext highlighter-rouge">Edit</code> folder, select <code class="language-plaintext highlighter-rouge">New</code> &gt; <code class="language-plaintext highlighter-rouge">Key</code> and name this <code class="language-plaintext highlighter-rouge">Command</code>.</li>
  <li>The <code class="language-plaintext highlighter-rouge">(Default)</code> entry should now exist in the new <code class="language-plaintext highlighter-rouge">Command</code> folder, and you should be able to set its value as described above.</li>
</ol>

<h2 id="registry-values-and-download-files-for-popular-autohotkey-script-editors">Registry values and download files for popular AutoHotkey script editors</h2>

<p><a href="https://www.autohotkey.com/docs/v1/lib/Edit.htm#Editors">There are many editors</a> that can be used to edit AutoHotkey scripts, <a href="https://www.the-automator.com/best-autohotkey-editors-ides/">some more popular than others</a>.
Most have extensions/plugins that should be installed for the best experience.
Below are some popular editors and their registry value to use.</p>

<p>I also included a .reg file that you can download and run to set the registry entry for you, so that you don’t need to make manual edits in the Registry Editor as shown above.</p>

<hr />

<p><a href="https://notepad-plus-plus.org/">Notepad++</a>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"C:\Program Files\Notepad++\notepad++.exe" "%1"
</code></pre></div></div>

<p><a href="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/NotepadPlusPlusAsDefaultAhkEditor.reg">Download Notepad++ .reg file</a></p>

<hr />

<p><a href="https://code.visualstudio.com/">Visual Studio Code</a>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"C:\Users\[YOUR USERNAME GOES HERE]\AppData\Local\Programs\Microsoft VS Code\Code.exe" "%1"
</code></pre></div></div>

<p>NOTE: You need to replace <code class="language-plaintext highlighter-rouge">[YOUR USERNAME GOES HERE]</code> with your actual username.</p>

<p>No .reg file to download is provided since the username in the executable file path will be unique to you.</p>

<hr />

<p><a href="https://www.autohotkey.com/scite4ahk/">SciTE4AutoHotkey</a>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"C:\Program Files\AutoHotkey\SciTE\SciTE.exe" "%1"
</code></pre></div></div>

<p>NOTE: SciTE4AutoHotkey has an option in the installer to set itself as the default editor for AutoHotkey scripts, so that you do not need to edit the registry manually.
Even if SciTE4AutoHotkey is already installed, you can run the installer again to set it as the default editor.
Or use the .reg file provided below.</p>

<p><a href="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/SciTE4AutoHotkeyAsDefaultAhkEditor.reg">Download SciTE4AutoHotkey .reg file</a></p>

<hr />

<p><a href="https://www.sublimetext.com">Sublime Text 3</a>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"C:\Program Files\Sublime Text 3\sublime_text.exe" "%1"
</code></pre></div></div>

<p><a href="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/SublimeText3AsDefaultAhkEditor.reg">Download Sublime Text 3 .reg file</a></p>

<h2 id="locating-the-executable-file-path">Locating the executable file path</h2>

<p>If you use a different editor and are not sure what the executable’s file path is, you can typically find it by:</p>

<ol>
  <li>Hit the <code class="language-plaintext highlighter-rouge">Windows Key</code> to open the Start Menu.</li>
  <li>Type the name of the editor you want to use.</li>
  <li>
    <p>Right-click on the editor and choose <code class="language-plaintext highlighter-rouge">Open file location</code>.</p>

    <p><img src="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/open-application-file-location-from-windows-start-menu.png" alt="Open the application file location from the Windows Start Menu" /></p>
  </li>
  <li>That should open up File Explorer.
    <ol>
      <li>If File Explorer shows the .exe file, you can use its path.</li>
      <li>If File Explorer shows a shortcut to the application, right-click on the shortcut and choose <code class="language-plaintext highlighter-rouge">Properties</code>.
        <ol>
          <li>
            <p>In the shortcut properties window, click the <code class="language-plaintext highlighter-rouge">Open File Location</code> button to open a new File Explorer window containing the .exe file.</p>

            <p><img src="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/open-file-location-from-shortcut-file-properties.png" alt="Open the shortcut file location from the shortcut properties window" /></p>
          </li>
        </ol>
      </li>
    </ol>
  </li>
  <li>
    <p>Copy the path of the executable file and use that as the value for the registry key.</p>

    <p><img src="/assets/Posts/2023-03-02-Change-the-default-AutoHotkey-script-editor/copy-path-to-executable-file.png" alt="Copy the path of the executable file from File Explorer" /></p>
  </li>
</ol>

<p>In this example above, the path to the Visual Studio Code executable is “C:\Users\Dan.Schroeder\AppData\Local\Programs\Microsoft VS Code\Code.exe”.</p>

<h2 id="other-ways-to-open-a-script-in-the-default-editor">Other ways to open a script in the default editor</h2>

<p>You can also use the following AutoHotkey code to open an ahk script in the default editor:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">filePath</span> <span class="p">=</span> <span class="n">C</span><span class="p">:</span><span class="err">\</span><span class="n">Path</span><span class="err">\</span><span class="n">To</span><span class="err">\</span><span class="n">AutoHotkey</span><span class="err">\</span><span class="n">Script</span><span class="p">.</span><span class="n">ahk</span>
<span class="n">Run</span><span class="p">,</span> <span class="n">edit</span> <span class="p">%</span><span class="n">filePath</span><span class="p">%,,</span><span class="n">UseErrorLevel</span>
<span class="k">if</span> <span class="p">(%</span><span class="n">ErrorLevel</span><span class="p">%</span> <span class="p">=</span> <span class="n">ERROR</span><span class="p">)</span>
    <span class="n">Run</span><span class="p">,</span> <span class="s">"notepad"</span> <span class="s">"%filePath%"</span>
</code></pre></div></div>

<p>In the code above, if an error occurs trying to open the ahk script in the default editor, it will open in Notepad instead.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Now when you right-click on an AutoHotkey script file and choose <code class="language-plaintext highlighter-rouge">Edit Script</code>, it will open in your preferred editor, making your life just a little more convenient.</p>

<p>Hopefully you’ve found this helpful.</p>

<p>Happy scripting!</p>

<blockquote>
  <p>Shameless Plug: <a href="https://github.com/deadlydog/AHKCommandPicker">AHK Command Picker</a> is my free open-source project that allows you to use a GUI picker instead of having to remember a ton of keyboard shortcuts for your AutoHotkey scripts and hotkeys.
It is essentially a quick launcher for any AutoHotkey code.
Check it out if you are interested!</p>
</blockquote>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="Productivity" /><category term="Editor" /><category term="AutoHotkey" /><category term="Productivity" /><category term="Editor" /><category term="AHK" /><summary type="html"><![CDATA[The Windows context menu provides an Edit Script option for AutoHotkey .ahk files. Unfortunately, it defaults to opening in Notepad, which is not a great editor for AutoHotkey scripts.]]></summary></entry><entry><title type="html">Run PowerShell as another user</title><link href="https://blog.danskingdom.com/Run-PowerShell-as-another-user/" rel="alternate" type="text/html" title="Run PowerShell as another user" /><published>2022-10-10T00:00:00+00:00</published><updated>2022-10-10T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Run-PowerShell-as-another-user</id><content type="html" xml:base="https://blog.danskingdom.com/Run-PowerShell-as-another-user/"><![CDATA[<p>I was looking for a way to run a PowerShell script as another user in our deployment pipeline.
There are many reasons you might want to do this; for me I needed to run some commands against a database that only supported Integrated Security, not SQL logins, and thus my script needed to run as the domain user with appropriate database permissions.</p>

<h2 id="run-as-different-user-on-the-local-computer">Run as different user on the local computer</h2>

<p>The trick to running PowerShell on the local machine as a different user is to use <code class="language-plaintext highlighter-rouge">Enter-PSSession</code> (<a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enter-pssession?view=powershell-7.2">see the docs</a>) or <code class="language-plaintext highlighter-rouge">Invoke-Command</code> (<a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-7.2">see the docs</a>) and specify <code class="language-plaintext highlighter-rouge">localhost</code> for the <code class="language-plaintext highlighter-rouge">-ComputerName</code>.</p>

<p>Use <code class="language-plaintext highlighter-rouge">Enter-PSSession</code> when running commands interactively, and use <code class="language-plaintext highlighter-rouge">Invoke-Command</code> to run code in a script.</p>

<p><strong>NOTE:</strong> To connect to <code class="language-plaintext highlighter-rouge">localhost</code> you will need to be running PowerShell as admin.
If you are not running as admin, you will get an <code class="language-plaintext highlighter-rouge">Access Denied</code> error message.
See the later section for ways to deal with this.</p>

<p>If you are running PowerShell interactively, you can have it prompt for the credentials of the user that you want to run as like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$credential</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Credential</span><span class="w"> </span><span class="c"># You will be prompted for the username and password here.</span><span class="w">

</span><span class="n">Enter-PSSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nx">localhost</span><span class="w"> </span><span class="nt">-Credential</span><span class="w"> </span><span class="nv">$credential</span><span class="w">

</span><span class="c"># Run your PowerShell commands here.</span><span class="w">

</span><span class="n">Exit-PSSession</span><span class="w">
</span></code></pre></div></div>

<p>In a non-interactive automated script, you can provide the username and password like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$username</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Your.Username'</span><span class="w">
</span><span class="nv">$password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Password in plain text'</span><span class="w">
</span><span class="nv">$securePassword</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ConvertTo-SecureString</span><span class="w"> </span><span class="nv">$password</span><span class="w"> </span><span class="nt">-AsPlainText</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="nv">$credential</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Management.Automation.PSCredential</span><span class="w"> </span><span class="p">(</span><span class="nv">$username</span><span class="p">,</span><span class="w"> </span><span class="nv">$securePassword</span><span class="p">)</span><span class="w">

</span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nx">localhost</span><span class="w"> </span><span class="nt">-Credential</span><span class="w"> </span><span class="nv">$credential</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="c"># Your PowerShell code goes here.</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>NOTE:</strong> Never store the plain text password in source control.
Instead, it should be injected into the script at runtime by some other mechanism, or pulled from a secrets store like Azure Key Vault.</p>

<p>Aside: You may prefer to define your scriptblock as a variable to help better organize your code, like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">scriptblock</span><span class="p">]</span><span class="w"> </span><span class="nv">$scriptBlock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="c"># Your PowerShell code goes here.</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="s1">'SomeServer.OnYour.Domain'</span><span class="w"> </span><span class="nt">-Credential</span><span class="w"> </span><span class="nv">$credential</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$scriptBlock</span><span class="w">
</span></code></pre></div></div>

<h2 id="run-as-different-user-on-a-remote-computer">Run as different user on a remote computer</h2>

<p>If you want to run the PowerShell commands on a different computer, you can simply replace <code class="language-plaintext highlighter-rouge">localhost</code> with the remote computer in the <code class="language-plaintext highlighter-rouge">-ComputerName</code> parameter.
e.g.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Enter-PSSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="s1">'SomeServer.OnYour.Domain'</span><span class="w"> </span><span class="nt">-Credential</span><span class="w"> </span><span class="nv">$credential</span><span class="w">
</span></code></pre></div></div>

<p>When connecting to a remote computer you do not need to worry about running the local PowerShell session as admin, as PSRemoting always connects as admin since only administrators are allowed to run commands remotely.</p>

<p>You can also specify the <code class="language-plaintext highlighter-rouge">-Authentication</code> parameter to specify the authentication method to use, such as CredSSP or Kerberos.
Different authentication modes may require additional configuration setup on the local and remote computers.</p>

<p>If you want to connect to the remote computer over SSH instead of WinRM (Windows Remote Management), then use the <code class="language-plaintext highlighter-rouge">-HostName</code> parameter instead of <code class="language-plaintext highlighter-rouge">-ComputerName</code>.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Enter-PSSession</span><span class="w"> </span><span class="nt">-HostName</span><span class="w"> </span><span class="s1">'SomeUser@SomeServer.OnYour.Domain'</span><span class="w">
</span></code></pre></div></div>

<p>SSH requires PowerShell 6 or later, and supports both password (for interactive sessions) and key-based user authentication.
<a href="https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/ssh-remoting-in-powershell-core?view=powershell-7.2">See the docs</a> for more details on using SSH.</p>

<p>You can read more about WinRM and PowerShell remoting in the docs <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote?view=powershell-7.2">here</a> and <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_requirements?view=powershell-7.2">here</a>.</p>

<h2 id="needing-to-run-powershell-as-admin">Needing to run PowerShell as admin</h2>

<p>As mentioned above, running commands against <code class="language-plaintext highlighter-rouge">localhost</code> requires you to be running PowerShell as admin.
This is typically easy to do if you are running PowerShell interactively (assuming your user is an administrator).</p>

<p><a href="https://adamtheautomator.com/powershell-run-as-administrator/">This blog</a> describes many different ways that you can launch the interactive PowerShell command prompt as admin.
Near the end it also mentions two ways to run a PowerShell script as admin: using <code class="language-plaintext highlighter-rouge">Start-Process PowerShell -Verb RunAs</code>, and using the Windows Task Scheduler.</p>

<p>If we need to run a PowerShell script as admin in another system however, such as a CI/CD pipeline in Azure DevOps Pipelines or GitHub Actions, we don’t always have control over if the PowerShell process is started as admin or not.</p>

<p><a href="https://stackoverflow.com/questions/7690994/running-a-command-as-administrator-using-powershell">This StackOverflow post</a> describes a few different ways to run a PowerShell script as admin, including this code snippet that you can put at the top of your script to have a it elevate itself to run as admin:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-NOT</span><span class="w"> </span><span class="p">([</span><span class="n">Security.Principal.WindowsPrincipal</span><span class="p">][</span><span class="n">Security.Principal.WindowsIdentity</span><span class="p">]::</span><span class="n">GetCurrent</span><span class="p">())</span><span class="o">.</span><span class="nf">IsInRole</span><span class="p">([</span><span class="n">Security.Principal.WindowsBuiltInRole</span><span class="p">]</span><span class="w"> </span><span class="s2">"Administrator"</span><span class="p">))</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nv">$arguments</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"&amp; '"</span><span class="w"> </span><span class="o">+</span><span class="nv">$myinvocation</span><span class="o">.</span><span class="nf">mycommand</span><span class="o">.</span><span class="nf">definition</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s2">"'"</span><span class="w">
  </span><span class="n">Start-Process</span><span class="w"> </span><span class="nx">powershell</span><span class="w"> </span><span class="nt">-Verb</span><span class="w"> </span><span class="nx">runAs</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="nv">$arguments</span><span class="w">
  </span><span class="kr">Break</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="c"># Your script code goes here.</span><span class="w">
</span></code></pre></div></div>

<p>This approach may work for your needs.
While this approached worked for scripts on my local machine, it did not work for scripts running in my Azure DevOps Pipelines; I still received the <code class="language-plaintext highlighter-rouge">Access Denied</code> error message.
Even if it had worked, one problem you may have spotted is that it starts up a new PowerShell process to run the script as admin.
This would have resulted in the script output not getting captured by the deployment pipeline, and not being available for monitoring and debugging.</p>

<p>The workaround I ended up using for my specific scenario of connecting to a remote database and performing some operations was to use <code class="language-plaintext highlighter-rouge">Invoke-Command</code> to run the PowerShell scriptblock as a different user from a remote server.
This isn’t an ideal solution as it introduces a dependency on another server simply for the purpose of running some PowerShell code, and can introduce additional security risks by enabling WinRM on the server if it’s unneeded otherwise, but it was the best solution I could make work.
We actually have several different pipelines that have this need, so I created a small server whose main purpose is to run PowerShell scripts as a different user.</p>

<p>If you have any other potential solutions, please let me know in the comments below.
I hope you found this post useful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="PowerShell" /><summary type="html"><![CDATA[I was looking for a way to run a PowerShell script as another user in our deployment pipeline. There are many reasons you might want to do this; for me I needed to run some commands against a database that only supported Integrated Security, not SQL logins, and thus my script needed to run as the domain user with appropriate database permissions.]]></summary></entry><entry><title type="html">Ensure Windows power settings are not slowing your CPU</title><link href="https://blog.danskingdom.com/Ensure-Windows-power-settings-are-not-slowing-your-CPU/" rel="alternate" type="text/html" title="Ensure Windows power settings are not slowing your CPU" /><published>2022-05-03T00:00:00+00:00</published><updated>2022-05-03T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Ensure-Windows-power-settings-are-not-slowing-your-CPU</id><content type="html" xml:base="https://blog.danskingdom.com/Ensure-Windows-power-settings-are-not-slowing-your-CPU/"><![CDATA[<p>My computer was sometimes very slow and unresponsive.
It turned out the Windows power settings were throttling my processor speed.</p>

<p>This post will show you how to check if your CPU is being throttled by the Windows power settings, and how to correct it.</p>

<h2 id="check-if-your-cpu-is-throttled">Check if your CPU is throttled</h2>

<p>The way in which the Windows power settings may be throttling your CPU is by setting the <code class="language-plaintext highlighter-rouge">CPU Maximum Frequency</code> to be less than 100%.</p>

<p>To quickly get a sense for what your CPU Maximum Frequency is set to, open the <code class="language-plaintext highlighter-rouge">Resource Monitor</code> app (by hitting the Windows Key and searching for <code class="language-plaintext highlighter-rouge">Resource Monitor</code>) and then navigate to the <code class="language-plaintext highlighter-rouge">CPU</code> tab.</p>

<p>In the Processes header near the top, you will see <code class="language-plaintext highlighter-rouge">CPU Usage</code> and <code class="language-plaintext highlighter-rouge">Maximum Frequency</code> percentage values.
These values will likely fluctuate up and down as you watch them.
On the right side of the window you will also see a <code class="language-plaintext highlighter-rouge">CPU - Total</code> graph showing the <code class="language-plaintext highlighter-rouge">CPU Usage</code> (green line), and <code class="language-plaintext highlighter-rouge">Maximum Frequency</code> (blue line) over time.</p>

<p><img src="/assets/Posts/2022-05-03-Ensure-Windows-power-settings-are-not-slowing-your-CPU/ResourceMonitorCpuFrequencyScreenshot.png" alt="Resource Monitor screenshot" /></p>

<p>What you are looking for is to see if the Maximum Frequency never goes above a given value (e.g. 30%), and also if the green CPU Usage line is often hitting the blue Maximum Frequency line.
This should be especially apparent if you watch it while doing something CPU intensive, like playing a game or running multiple applications at once.</p>

<p>For example, I noticed that my PC would often become very sluggish and slow to respond, even though Task Manager showed my CPU was not busy and the memory usage was low.
In the Resource Manager though, I saw the <code class="language-plaintext highlighter-rouge">CPU Maximum Frequency</code> never went above 30%, and that the green <code class="language-plaintext highlighter-rouge">CPU Usage</code> line was continually bumping up against the blue Maximum Frequency line.
This meant that even though my CPU wasn’t particularly busy, it was being throttled to not be able to run at more than 30% of it’s capacity.
Windows was throttling it, preventing it from running at it’s full speed, and making my PC slow to respond.</p>

<p>See <a href="https://superuser.com/questions/256921/what-does-the-maximum-frequency-number-mean-in-the-windows-resource-monitor">this Super User question</a> for a bit more info on the CPU Maximum Frequency.
In short, Windows may intentionally throttle your CPU to save power.
If you are not using a power saver mode though, then you may want to manually modify your current power plan’s settings to have the CPU run at full power.</p>

<h2 id="modify-the-cpu-max-frequency">Modify the CPU Max Frequency</h2>

<p>Following <a href="https://gigabytekingdom.com/what-is-cpu-maximum-frequency/">this guide</a> I was able to increase my CPU Maximum Frequency to 100%, and I’ll present the steps I took here.</p>

<h3 id="enable-viewing-the-maximum-processor-frequency-value">Enable viewing the Maximum Processor Frequency value</h3>

<p>In order for the <code class="language-plaintext highlighter-rouge">Maximum processor frequency</code> value to be shown in the Windows Power Options Advanced Settings, we need to first enable it.
This only needs to be done once:</p>

<ol>
  <li>Open a command prompt as an administrator.
    <ol>
      <li>Hit the Windows Key and type <code class="language-plaintext highlighter-rouge">Command prompt</code>.</li>
      <li>Right click on the <code class="language-plaintext highlighter-rouge">Command Prompt</code> app and choose <code class="language-plaintext highlighter-rouge">Run as administrator</code>.</li>
    </ol>
  </li>
  <li>In the command prompt window, type the following and hit enter: <code class="language-plaintext highlighter-rouge">powercfg -attributes SUB_PROCESSOR 75b0ae3f-bce0-45a7-8c89-c9611c25e100 -ATTRIB_HIDE</code></li>
  <li>Restart your computer.</li>
</ol>

<p><img src="/assets/Posts/2022-05-03-Ensure-Windows-power-settings-are-not-slowing-your-CPU/EnableMaxProcessorFrequencyScreenshot.png" alt="Enable Maximum Processor Frequency setting in Power Options screenshot" /></p>

<h3 id="view-the-maximum-processor-frequency-value">View the Maximum Processor Frequency value</h3>

<p>To view the <code class="language-plaintext highlighter-rouge">Maximum processor frequency</code> value in the Power Options:</p>

<ol>
  <li>Open the Windows Power and Sleep settings.
    <ol>
      <li>Hit the Windows Key and type “Power Settings” and choose the <code class="language-plaintext highlighter-rouge">Power &amp; sleep settings</code> app.</li>
    </ol>
  </li>
  <li>Under <code class="language-plaintext highlighter-rouge">Related settings</code> click the <code class="language-plaintext highlighter-rouge">Additional power settings</code> link.</li>
  <li>In the Power Options window that opened, find which power plan you are using and click the <code class="language-plaintext highlighter-rouge">Change plan settings</code> link.</li>
  <li>In the Edit Plan Settings window that opened, click the <code class="language-plaintext highlighter-rouge">Change advanced power settings</code> link.</li>
  <li>In the Advanced Settings window that opened, expand the <code class="language-plaintext highlighter-rouge">Process power management</code> node and then the <code class="language-plaintext highlighter-rouge">Maximum processor frequency</code> node.</li>
  <li>Here you will see the current values for the <code class="language-plaintext highlighter-rouge">Maximum processor frequency</code> when running on battery and when plugged in.</li>
</ol>

<p><img src="/assets/Posts/2022-05-03-Ensure-Windows-power-settings-are-not-slowing-your-CPU/NavigateToAdvancedPowerSettings.gif" alt="Navigate to advanced power settings gif" /></p>

<p>Initially when I navigated here on my PC both values were set to 0, but my <code class="language-plaintext highlighter-rouge">CPU Maximum Frequency</code> in the Resource Manager was still capped at 30% for some reason.</p>

<p>Note that some power plans, such as power saver, may intentionally lower the <code class="language-plaintext highlighter-rouge">Maximum processor frequency</code> value in order to save power.
In my case I only had one power plan, which was <code class="language-plaintext highlighter-rouge">Balanced</code>, so I wanted it to be able to leverage the full normal processor speed.
Also, I wanted it to run at full speed whether on battery or plugged in, so I wanted to change both values.</p>

<p>If you want to change these values, Windows expects them to be in MHz, not percent, so you’ll need to know your CPU’s frequency in MHz.</p>

<h3 id="find-your-cpus-frequency">Find your CPUs frequency</h3>

<p>To find your CPU’s frequency:</p>

<ol>
  <li>Open <code class="language-plaintext highlighter-rouge">System Information</code> by hitting the Windows Key and searching for <code class="language-plaintext highlighter-rouge">System Information</code>.</li>
  <li>In the System Information window’s System Summary, locate the <code class="language-plaintext highlighter-rouge">Processor</code> item and look at the GHz or MHz value.</li>
</ol>

<p><img src="/assets/Posts/2022-05-03-Ensure-Windows-power-settings-are-not-slowing-your-CPU/SystemInformationCpuFrequencyScreenshot.png" alt="System Information screenshot" /></p>

<p>1 GHz = 1000 MHz, so if it only lists your processor speed in GHz, you’ll need to multiply it by 1000 to get the MHz value.
e.g. 2.3 GHz = 2300 MHz.</p>

<h3 id="update-the-maximum-processor-frequency-value">Update the Maximum Processor Frequency value</h3>

<p>Now that you know your processor frequency speed in MHz, you can update the values in the Advanced Power Settings window.</p>

<ol>
  <li>Go back to the Power Options Advanced Settings window.</li>
  <li>Find your way back to the <code class="language-plaintext highlighter-rouge">Maximum processor frequency</code> node.</li>
  <li>Update the <code class="language-plaintext highlighter-rouge">On battery</code> and <code class="language-plaintext highlighter-rouge">Plugged in</code> values to be equal to your CPU’s frequency in MHz.
    <ul>
      <li>Be sure not to use a value greater than your CPU’s frequency, as that may overclock your CPU which can cause overheating and potentially damage your CPU hardware.</li>
    </ul>
  </li>
  <li>Hit OK to save the settings.</li>
  <li>Restart your computer for the change to take effect.</li>
</ol>

<p><img src="/assets/Posts/2022-05-03-Ensure-Windows-power-settings-are-not-slowing-your-CPU/UpdatedCpuMaximumFrequencySettingScreenshot.png" alt="Updated Advanced Power Settings screenshot" /></p>

<h3 id="verifying-the-change-worked">Verifying the change worked</h3>

<p>Head back into the Resource Monitor and verify that the <code class="language-plaintext highlighter-rouge">CPU Maximum Frequency</code> is now going higher than it was before, reaching up to 99% or 100%.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We’ve seen how to quickly check your CPU’s maximum frequency to see if it’s throttling your CPU, as well as how to find your CPU’s frequency speed.
We also showed how to view and edit the Windows power settings to ensure it’s not throttling your processor speed, so your PC runs at full speed.</p>

<p>Happy computing!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows" /><category term="Performance" /><category term="Windows" /><category term="Performance" /><summary type="html"><![CDATA[My computer was sometimes very slow and unresponsive. It turned out the Windows power settings were throttling my processor speed.]]></summary></entry><entry><title type="html">Use WinGet to install and update your Windows apps</title><link href="https://blog.danskingdom.com/Use-WinGet-to-install-and-update-your-Windows-apps/" rel="alternate" type="text/html" title="Use WinGet to install and update your Windows apps" /><published>2022-02-18T00:00:00+00:00</published><updated>2022-04-15T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Use-WinGet-to-install-and-update-your-Windows-apps</id><content type="html" xml:base="https://blog.danskingdom.com/Use-WinGet-to-install-and-update-your-Windows-apps/"><![CDATA[<p>I installed all my Windows apps on my new computer with one line using WinGet.</p>

<p>It was simple.
It was easy.
Here’s what I did.</p>

<h2 id="what-is-winget">What is WinGet</h2>

<p><a href="https://docs.microsoft.com/en-us/windows/package-manager/winget/">WinGet</a> is Microsoft’s native command line application installer, and it now comes pre-installed with Windows 10+.
If you have Windows 7 or 8, you’ll need to manually install it via <a href="https://www.microsoft.com/en-us/p/app-installer/9nblggh4nns1">the <code class="language-plaintext highlighter-rouge">App Installer</code> app in the Microsoft Store</a>.</p>

<p>You can think of WinGet like the Microsoft Store, but for the command line, and it actually has the apps you’re looking for.
Not just UWP (Universal Windows Platform) apps like the Microsoft Store has, but the apps you’ve been installing for 2 decades.
Apps installed via an .exe or .msi.
Win32 apps.</p>

<p>Not only does it support every type of app that you could install manually, the app catalog is huge; currently over 3000 apps.
There wasn’t a single app I wanted that I couldn’t find on WinGet.</p>

<h2 id="head-to-wingetrun">Head to WinGet.run</h2>

<p>Don’t believe me?
Head over to <a href="https://winget.run">WinGet.run</a> and see for yourself.</p>

<p>Search the name of the app you’re looking for and see if it’s there.</p>

<p>Once you’ve found it, click the <code class="language-plaintext highlighter-rouge">Copy command</code> link and it will copy the WinGet command you can use to install that app from the command line.</p>

<p><img src="/assets/Posts/2022-02-18-Use-WinGet-to-install-and-update-your-Windows-apps/WinGetRunWebsiteSearchScreenshot.png" alt="WinGet.run website search screenshot" /></p>

<p>The command copied to your clipboard will look like:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Spotify.Spotify
</code></pre></div></div>

<h2 id="installing-apps-with-winget">Installing apps with WinGet</h2>

<p>Open a command prompt.
You can use cmd, PowerShell, Windows Terminal, Bash, or whatever you prefer.</p>

<p>Paste the command you copied from WinGet.run into the command prompt, hit enter, and watch your app get installed.</p>

<p><img src="/assets/Posts/2022-02-18-Use-WinGet-to-install-and-update-your-Windows-apps/WinGetInstallSpotifyScreenshot.png" alt="WinGet install Spotify screenshot" /></p>

<p>You can see here that Spotify was downloaded and installed in 24 seconds.
In this case, Spotify installed without prompting me for anything.</p>

<p>You may get prompted by Windows UAC to allow the app installer to run if it requires elevated privileges.
This can be avoided by opening your command prompt as administrator.</p>

<p><img src="/assets/Posts/2022-02-18-Use-WinGet-to-install-and-update-your-Windows-apps/RunCommandPromptAsAdministratorScreenshot.png" alt="Run command prompt as administrator" /></p>

<p>Some apps show the install wizard while it installs, others don’t.
From my experience, most apps that do show the install wizard don’t prompt you to do anything; you can just see it working.
If you like, you can provide the <code class="language-plaintext highlighter-rouge">--silent</code> parameter to suppress showing the installer and have it install with the default options, if the app’s installer supports it.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Spotify.Spotify <span class="nt">--silent</span>
</code></pre></div></div>

<p>I keep a text file in my OneDrive of apps that I like to install anytime I wipe my computer.
That text file now looks like this:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">===========================================================</span>
On WinGet: https://winget.run
<span class="o">===========================================================</span>
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> 7zip.7zip
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Audacity.Audacity
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Microsoft.AzureCLI
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Lexikos.AutoHotkey
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Google.Chrome
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Discord.Discord
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Ditto.Ditto
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Docker.DockerDesktop
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> File-New-Project.EarTrumpet
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> voidtools.Everything
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Telerik.Fiddler.Classic
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Mozilla.Firefox
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Git.Git
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> GitExtensionsTeam.GitExtensions
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> GoLang.Go
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> StefanTools.grepWin
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> HandBrake.HandBrake
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> icsharpcode.ILSpy
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> JoachimEibl.KDiff3
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Notepad++.Notepad++
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> JanDeDobbeleer.OhMyPosh
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Postman.Postman
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Microsoft.PowerShell
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Microsoft.PowerToys
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Python.Python.3
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> RubyInstallerTeam.RubyWithDevKit
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Spotify.Spotify <span class="o">(</span>Cannot <span class="nb">install </span>when running as administrator<span class="o">)</span>
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Microsoft.SQLServerManagementStudio
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Valve.Steam
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> ShareX.ShareX
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Toggl.TogglDesktop
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> TortoiseGit.TortoiseGit
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Microsoft.VisualStudio.2022.Enterprise <span class="o">(</span>Will still need to <span class="k">select </span>your specific workloads afterward<span class="o">)</span>
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Microsoft.VisualStudioCode <span class="o">(</span>Will not <span class="nb">install </span>the File Explorer context menu shortcuts by default<span class="p">;</span> have to manually <span class="nb">install </span>to check those boxes <span class="k">in </span>the installer<span class="o">)</span>
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> VideoLAN.VLC
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Radionomy.Winamp <span class="o">(</span>Latest version does not allow queuing songs, so better to use 5.6 from https://archive.org/download/winamp5666_full_all_redux<span class="o">)</span>
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Microsoft.WindowsTerminal
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> AntibodySoftware.WizTree
winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Zoom.Zoom
</code></pre></div></div>

<p>Here I’ve got each install command on a separate line, but you can combine them into a single line to copy-paste into the command prompt in one shot by separating them with a semicolon, like this:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> 7zip.7zip<span class="p">;</span>winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Lexikos.AutoHotkey<span class="p">;</span>winget <span class="nb">install</span> <span class="nt">-e</span> <span class="nt">--id</span> Google.Chrome
</code></pre></div></div>

<p>WinGet installs apps one at a time, so if you give it a large number of apps to install or update, it may take a while depending on how long each app takes to download and install.</p>

<h2 id="updating-apps">Updating apps</h2>

<p>Keeping your apps up-to-date with WinGet is a breeze.
Just use the <code class="language-plaintext highlighter-rouge">winget upgrade</code> command.
If you don’t pass any parameters to it, it will scan your computer and tell you which apps can be updated.</p>

<p><img src="/assets/Posts/2022-02-18-Use-WinGet-to-install-and-update-your-Windows-apps/WinGetUpgradeCommandScreenshot.png" alt="WinGet upgrade command screenshot" /></p>

<p>Notice that there are apps in this list that I didn’t install with WinGet, such as “Microsoft Azure Storage Explorer”.
That’s right, WinGet can even upgrade apps that it didn’t install!</p>

<p>From there you can upgrade a single app using the <code class="language-plaintext highlighter-rouge">--id</code> or <code class="language-plaintext highlighter-rouge">--name</code> parameter:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget upgrade <span class="nt">--id</span> Git.Git
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget upgrade <span class="nt">--name</span> Git
</code></pre></div></div>

<p>Or you can easily upgrade every application using:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget upgrade <span class="nt">--all</span>
</code></pre></div></div>

<p><img src="/assets/Posts/2022-02-18-Use-WinGet-to-install-and-update-your-Windows-apps/WinGetUpgradeAllCommandScreenshot.png" alt="WinGet upgrade all command screenshot" /></p>

<h2 id="more-commands">More commands</h2>

<p><a href="https://winget.run">WinGet.run</a> is handy for searching the app catalog, but you can also search right from the command line if you prefer using <code class="language-plaintext highlighter-rouge">winget search &lt;app name&gt;</code></p>

<p><img src="/assets/Posts/2022-02-18-Use-WinGet-to-install-and-update-your-Windows-apps/WinGetCommandLineSearchScreenshot.png" alt="WinGet command line search screenshot" /></p>

<p>To see all of the commands check out <a href="https://docs.microsoft.com/en-us/windows/package-manager/winget/">the official MS documentation</a>, or just type <code class="language-plaintext highlighter-rouge">winget</code> on the command line and you’ll get the full menu.</p>

<p><img src="/assets/Posts/2022-02-18-Use-WinGet-to-install-and-update-your-Windows-apps/WinGetMenuScreenshot.png" alt="WinGet menu screenshot" /></p>

<p>You can also use the <code class="language-plaintext highlighter-rouge">-?</code> flag to get more information about how to use a command, such as:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winget upgrade -?
</code></pre></div></div>

<h2 id="i-already-use-chocolatey--scoop--etc">I already use Chocolatey / Scoop / etc</h2>

<p>If you’re already using an alternate Windows package manager like Chocolatey or Scoop and are happy with it, stick with it.
WinGet is still relatively new, so more mature alternatives may provide you with functionality that WinGet doesn’t have yet, or have a larger catalog with more obscure apps that you use.
One feature I’m personally looking forward to is <a href="https://github.com/microsoft/winget-cli/issues/476">locking an app to a specific version</a> so it doesn’t get updated when using the <code class="language-plaintext highlighter-rouge">winget update --all</code> command.</p>

<p>I enjoy that WinGet is already installed by default, has every app I’ve looked for, and that I can use it to update apps that I’ve manually installed too.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We’ve seen how easy it is to install apps using WinGet.
Not only is it easy to install apps, but it’s also easy to update them, even if they weren’t installed by WinGet.
The WinGet catalog is huge, so it likely has every app you want on it.
And one of the best parts, it’s already pre-installed on Windows 10+, so there’s no setup required.</p>

<h3 id="aside">Aside</h3>

<p>Microsoft is currently looking at allowing the Microsoft Store to install Win32 apps as well.
Hopefully once that’s complete it will provide a similar experience to WinGet and pull apps from the same catalog.
It would be great since the Microsoft Store apps not only auto-update by themselves, but also roam with your account, so once you log in on a computer it installs your apps with zero effort from you.
Unfortunately that future is not a reality yet, and it’s too early to speculate what that user experience will be like, so in the meantime we can use WinGet to make our lives easier.</p>

<p>Happy installing!</p>

<blockquote>
  <p>Update: There is one app that I couldn’t find on WinGet yet, which is <code class="language-plaintext highlighter-rouge">Paint.Net</code>.
There’s <a href="https://forums.getpaint.net/topic/118574-please-add-paintnet-to-the-available-packages-for-windows-package-manager-winget/">a request open</a> for the app developer to considering putting it on WinGet.</p>
</blockquote>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows" /><category term="Productivity" /><category term="Windows" /><category term="Productivity" /><summary type="html"><![CDATA[I installed all my Windows apps on my new computer with one line using WinGet.]]></summary></entry><entry><title type="html">Communicate intent more effectively with Conventional Commit and Comment messages</title><link href="https://blog.danskingdom.com/2022-01-23-Conventional-Commit-and-Comment-messages/" rel="alternate" type="text/html" title="Communicate intent more effectively with Conventional Commit and Comment messages" /><published>2022-01-23T00:00:00+00:00</published><updated>2022-01-23T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Conventional-commit-and-comment-messages</id><content type="html" xml:base="https://blog.danskingdom.com/2022-01-23-Conventional-Commit-and-Comment-messages/"><![CDATA[<p>Conventional Commits and Conventional Comments can save you and your team time and prevent miscommunications by expressing intent more clearly.
Let’s look at what they are and why you would want to adopt them.</p>

<h2 id="-conventional-commits">✅ Conventional Commits</h2>

<p><a href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits</a> is a convention for making your source control commit messages clearer and easier to understand by using standard labels in your commit messages.
It allows you to, at a glance, tell if the commit is a bug fix, a new feature, a refactor, etc.
From what I understand, it basically took what <a href="https://github.com/commitizen/cz-cli">Commitizen</a> was doing and formalized it into a standard spec.</p>

<p>Here’s an example of a regular commit message:</p>

<blockquote>
  <p>Show subtotal of items in cart</p>
</blockquote>

<p>And what the same commit message might look like using Conventional Commit messages:</p>

<blockquote>
  <p>feat (cart): Show subtotal of items in cart</p>
</blockquote>

<p>or</p>

<blockquote>
  <p>fix (cart): Show subtotal of items in cart</p>
</blockquote>

<p>Notice how in the first example it wasn’t clear if the change was introducing a new feature or fixing a bug, but it’s obvious in the second and third examples by use of the <code class="language-plaintext highlighter-rouge">feat</code> and <code class="language-plaintext highlighter-rouge">fix</code> labels.</p>

<p>Similarly, simply by looking at commit messages like:</p>

<blockquote>
  <p>test: Ensure cart total is correct</p>
</blockquote>

<p>and</p>

<blockquote>
  <p>docs: Update outdated hyperlink</p>
</blockquote>

<p>you know that the commits are making changes to tests and documentation, by use of the <code class="language-plaintext highlighter-rouge">test</code> and <code class="language-plaintext highlighter-rouge">docs</code> labels respectively, meaning no application or business logic code has been changed and the changes should be safe to promote to production.</p>

<p>Commit messages like:</p>

<blockquote>
  <p>feat!: Allow regex in search</p>

  <p>BREAKING CHANGE</p>
</blockquote>

<p>use the <code class="language-plaintext highlighter-rouge">!</code> and <code class="language-plaintext highlighter-rouge">BREAKING CHANGE</code> labels to make it obvious that the change is breaking backward compatibility, and further considerations should be made before merging the change into production.</p>

<p>In addition to making commit messages more human understandable, it also allows tooling to be built around them, like <a href="https://medium.com/agoda-engineering/automating-versioning-and-releases-using-semantic-release-6ed355ede742">automatic semantic versioning from commit messages</a> where the tool is able to calculate what the next <a href="https://semver.org">semantic version</a> should be based on the commit messages and the types of changes in them, removing the burden from a human having to figure out the new version number manually.</p>

<p>I also find using Conventional Commit messages helpful for Pull Request (PR) descriptions.
It allows reviewers to quickly scan the description and know what types of changes are included in the PR.
Azure DevOps has a handy feature that allows the commit messages to easily be copied into the PR description, which I’m hoping GitHub adopts soon.</p>

<p>I’ve been using Conventional Commit messages for years and find it very useful.
Be sure to <a href="https://www.conventionalcommits.org/en/v1.0.0/">check out the spec</a> to see the expected formatting and all of the defined labels.</p>

<h2 id="-conventional-comments">💬 Conventional Comments</h2>

<p><a href="https://conventionalcomments.org">Conventional Comments</a> is a similar concept to Conventional Commits, but it proposes labels to use in your Pull Request (PR) comments, with the goal being to make the intent of your code review comments more clear to other readers.</p>

<p>Here’s an example of a typical code review comment:</p>

<blockquote>
  <p>This code is so compact</p>
</blockquote>

<p>And what the same comment might look like using Conventional Comments:</p>

<blockquote>
  <p>praise: This code is so compact</p>
</blockquote>

<p>or</p>

<blockquote>
  <p>nitpick (non-blocking): This code is so compact</p>
</blockquote>

<p>Code styling is often a personal choice, and in the first example it’s not clear if the commenter likes how compact the code is, or if they would prefer it changed to be more readable.
In the second example, it’s clear the commenter is a fan of how compact the code is because they used the <code class="language-plaintext highlighter-rouge">praise</code> label.
In the third example, you can tell the commenter doesn’t like how compact the code is and would prefer it be changed, but that it shouldn’t be considered a blocker to getting the pull request merged in, since it’s using the <code class="language-plaintext highlighter-rouge">(non-blocking)</code> decorator.</p>

<p>Here’s some more example Conventional Comments:</p>

<blockquote>
  <p>issue: We should hide the checkout button</p>
</blockquote>

<p>Using the <code class="language-plaintext highlighter-rouge">issue</code> label makes it clear that there will be a problem if the checkout button is not hidden, and that it’s not just a “nice to have” suggestion.</p>

<blockquote>
  <p>thought: We could show a list of related products</p>
</blockquote>

<p>The <code class="language-plaintext highlighter-rouge">thought</code> label indicates that this comment shouldn’t block the PR, but that it’s something we should consider for a future change.</p>

<blockquote>
  <p>suggestion (if-minor): Add validation to the checkout form</p>
</blockquote>

<p>The <code class="language-plaintext highlighter-rouge">if-minor</code> decorator indicates to only consider the <code class="language-plaintext highlighter-rouge">suggestion</code> for this PR if it’s a minor change that’s easy to make, helping to avoid scope creep.</p>

<p>Be sure to <a href="https://conventionalcomments.org">check out the Conventional Comments spec</a> to see all of the defined labels.</p>

<p>Also be sure to check out <a href="https://a-hemdan.medium.com/conventional-comments-1f83f56a7a48">this blog post on Conventional Comments</a> which explains the concept further, and also contains some code that you can copy into GitHub to quickly create GitHub saved replies for each of the Conventional Comments labels.</p>

<p>There’s even <a href="https://chrome.google.com/webstore/detail/conventional-comments/pagggmojbbphjnpcjeeniigdkglamffk">a Chrome extension</a> (<a href="https://github.com/AbdallahHemdan/Conventional-Buttons">GitHub</a>) to help easily use them in your PR comments.</p>

<p>I only recently discovered this concept of Conventional Comments so I haven’t been using it as long, but it’s an excellent idea and I thought I’d share it 🙂.</p>

<h2 id="conclusion">Conclusion</h2>

<p>One thing I really like about both Conventional Commits and Conventional Comments is that the labels are intuitive enough that everyone can understand them, even people who have never heard of these concepts before.</p>

<p>When writing commit and comment messages, it can take a bit of time to remember the various different labels and decorators, but once you’ve used them for a short while it quickly becomes muscle memory.</p>

<p>Providing additional clarity to your messages by adding a simple label is an easy win, and it’s well worth the small time investment to learn and use the labels.</p>

<p>In short, Conventional Commits and Conventional Comments are great because they:</p>

<ul>
  <li>Make it easier for others (and your future self) to understand the intent of your code / comment.</li>
  <li>Allow you to quickly know at a glance what to expect in the rest of the message by setting the context.</li>
  <li>Enable tooling to be put in place to do things like automate manual tasks, collect metrics, etc.</li>
  <li>Are succinct, intuitive, easy to learn, and easy to use.</li>
</ul>

<p>Happy code committing and reviewing!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Software Development" /><category term="Comment" /><category term="Commit" /><category term="Pull Request" /><summary type="html"><![CDATA[Conventional Commits and Conventional Comments can save you and your team time and prevent miscommunications by expressing intent more clearly. Let’s look at what they are and why you would want to adopt them.]]></summary></entry><entry><title type="html">Get IIS events from Event Viewer using PowerShell</title><link href="https://blog.danskingdom.com/Get-IIS-events-from-Event-Viewer-using-PowerShell/" rel="alternate" type="text/html" title="Get IIS events from Event Viewer using PowerShell" /><published>2021-11-17T00:00:00+00:00</published><updated>2021-12-13T06:00:00+00:00</updated><id>https://blog.danskingdom.com/Get-IIS-events-from-event-viewer-using-PowerShell</id><content type="html" xml:base="https://blog.danskingdom.com/Get-IIS-events-from-Event-Viewer-using-PowerShell/"><![CDATA[<p>Sometimes services hosted in IIS don’t behave as we expect and we need to dig into what’s happening.
If the problem lies in your application’s code, you may need to rely on your application’s logging.
You can also <a href="https://stackify.com/beyond-iis-logs-find-failed-iis-asp-net-requests/">check the IIS logs</a> to find requests and their response codes.
Sometimes however, the problem lies underneath your application, in the server or IIS infrastructure or configuration.
Luckily, IIS records all sorts of events to the Windows Event Viewer to help us troubleshoot these types of issues.</p>

<p>Common types of IIS problem events recorded to the Event Viewer include:</p>

<ul>
  <li>Out of memory exceptions.</li>
  <li>Application pool restarts.</li>
  <li>Unable to load required modules/dlls.</li>
</ul>

<p>There’s <a href="https://www.howtogeek.com/123646/htg-explains-what-the-windows-event-viewer-is-and-how-you-can-use-it/">plenty of</a> <a href="https://www.dummies.com/computers/operating-systems/windows-10/how-to-use-event-viewer-in-windows-10/">other articles</a> describing how to use the Windows Event Viewer GUI, <a href="https://www.papertrail.com/solution/tips/windows-event-log-filtering-techniques/">filter in it</a>, and <a href="https://evotec.xyz/powershell-everything-you-wanted-to-know-about-event-logs/">query it using PowerShell</a>, so I won’t cover that here.</p>

<p><strong>Side note:</strong> In this post I will be using <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-eventlog?view=powershell-5.1">the Get-EventLog cmdlet</a>.
The MS docs say that <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.diagnostics/get-winevent?view=powershell-7.2">the Get-WinEvent cmdlet</a> is replacing it, but I find Get-EventLog much easier to work with and more intuitive, and the docs say it will be kept around for backward compatibility.</p>

<h2 id="getting-iis-events-using-powershell">Getting IIS events using PowerShell</h2>

<p>Below are some PowerShell commands which you can use to pull out just the IIS related events.
Depending on the type of event and it’s source, IIS may store it in different Log sources (Application or System), so we want to query them both.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">Application</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="o">*</span><span class="nx">W3SVC</span><span class="o">*</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w">
</span></code></pre></div></div>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">System</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="nx">WAS</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w">
</span></code></pre></div></div>

<p>If you want to ignore Informational events and only get Warnings and Errors, you can specify that.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">System</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="nx">WAS</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="nt">-EntryType</span><span class="w"> </span><span class="nx">Warning</span><span class="p">,</span><span class="nx">Error</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2021-11-17-Get-IIS-events-from-event-viewer-using-PowerShell/GetEventLogAsTable.png" alt="Get logs as table" /></p>

<p>You may notice that the Message column gets truncated.
To view the full details of the events, you can pipe them to <code class="language-plaintext highlighter-rouge">Format-List</code>.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">System</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="nx">WAS</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-List</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2021-11-17-Get-IIS-events-from-event-viewer-using-PowerShell/GetEventLogAsList.png" alt="Get logs as list" /></p>

<p>As with any PowerShell command, you can dump the results to a text file using <code class="language-plaintext highlighter-rouge">&gt; filename.txt</code>.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">System</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="nx">WAS</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-List</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="nx">C:\temp.txt</span><span class="w">
</span></code></pre></div></div>

<p>Or if you want the output dumped to both the console and a text file, use <code class="language-plaintext highlighter-rouge">Tee-Object</code>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">System</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="nx">WAS</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-List</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Tee-Object</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">C:\temp.txt</span><span class="w">
</span></code></pre></div></div>

<p>Some IIS logging events, such as request timeouts, are also written to the Windows Event Viewer.
If you want to also see those, you can include the ASP.NET source in your query.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">Application</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="o">*</span><span class="nx">W3SVC</span><span class="o">*</span><span class="p">,</span><span class="o">*</span><span class="nx">ASP</span><span class="o">*</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w">
</span></code></pre></div></div>

<h2 id="querying-multiple-servers">Querying multiple servers</h2>

<p>One of the best things about using PowerShell to query the Windows Event Viewer is that you can do it remotely.
Rather than having to remote desktop onto each server and run the above PowerShell commands (or poke around in the Windows Event Viewer GUI), you can query all of the servers from your local machine.
This assumes you have appropriate permissions and the server’s security configuration allows for it.</p>

<p>By default <code class="language-plaintext highlighter-rouge">Get-EventLog</code> queries the local machine.
To query other computers, simply provide them in the <code class="language-plaintext highlighter-rouge">-ComputerName</code> parameter:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nx">Server1</span><span class="p">,</span><span class="nx">Server2</span><span class="p">,</span><span class="nx">Server3</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="nx">Application</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="o">*</span><span class="nx">W3SVC</span><span class="o">*</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w">
</span></code></pre></div></div>

<p>Ideally you’d have an observability system in place to collect the events and logs from all of the servers so you could view and query them in a central place.
Unfortunately, many of us don’t have the time, money, or resources to put that in place, so this is the next best thing.</p>

<h2 id="copy-paste-ready-commands">Copy-paste ready commands</h2>

<p>Here’s a one-liner you can use to query both the Application and System log sources:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s1">'Application'</span><span class="p">,</span><span class="s1">'System'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="o">*</span><span class="nx">W3SVC</span><span class="o">*</span><span class="p">,</span><span class="nx">WAS</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>If you want to include IIS log alerts, such as request timeout events, include ASP.NET events:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s1">'Application'</span><span class="p">,</span><span class="s1">'System'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="o">*</span><span class="nx">W3SVC</span><span class="o">*</span><span class="p">,</span><span class="nx">WAS</span><span class="p">,</span><span class="o">*</span><span class="nx">ASP</span><span class="o">*</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>If you only want to see Warnings and Errors:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s1">'Application'</span><span class="p">,</span><span class="s1">'System'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="o">*</span><span class="nx">W3SVC</span><span class="o">*</span><span class="p">,</span><span class="nx">WAS</span><span class="p">,</span><span class="o">*</span><span class="nx">ASP</span><span class="o">*</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="nt">-EntryType</span><span class="w"> </span><span class="nx">Warning</span><span class="p">,</span><span class="nx">Error</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>If you want to see the full details of the events:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s1">'Application'</span><span class="p">,</span><span class="s1">'System'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Get-EventLog</span><span class="w"> </span><span class="nt">-LogName</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="nt">-Source</span><span class="w"> </span><span class="o">*</span><span class="nx">W3SVC</span><span class="o">*</span><span class="p">,</span><span class="nx">WAS</span><span class="p">,</span><span class="o">*</span><span class="nx">ASP</span><span class="o">*</span><span class="w"> </span><span class="nt">-Newest</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="nt">-EntryType</span><span class="w"> </span><span class="nx">Warning</span><span class="p">,</span><span class="nx">Error</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Format-List</span><span class="w">
</span></code></pre></div></div>

<p>I hope you find this information useful.</p>

<p>Happy IIS troubleshooting!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="IIS" /><category term="PowerShell" /><category term="IIS" /><category term="PowerShell" /><summary type="html"><![CDATA[Sometimes services hosted in IIS don’t behave as we expect and we need to dig into what’s happening. If the problem lies in your application’s code, you may need to rely on your application’s logging. You can also check the IIS logs to find requests and their response codes. Sometimes however, the problem lies underneath your application, in the server or IIS infrastructure or configuration. Luckily, IIS records all sorts of events to the Windows Event Viewer to help us troubleshoot these types of issues.]]></summary></entry><entry><title type="html">Find which Windows process has file in use</title><link href="https://blog.danskingdom.com/Find-which-Windows-process-has-file-in-use/" rel="alternate" type="text/html" title="Find which Windows process has file in use" /><published>2021-07-28T00:00:00+00:00</published><updated>2021-07-28T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Find-which-Windows-process-has-file-in-use</id><content type="html" xml:base="https://blog.danskingdom.com/Find-which-Windows-process-has-file-in-use/"><![CDATA[<p>When a file or directory is in use by a process (application), you will not be able to delete or modify the file / directory.
Unfortunately it’s not always obvious which process is using the file.
Luckily, there is an easy way to find out without the need of 3rd party tools, which is what we’ll look at in this post.</p>

<p>If you prefer videos, you can <a href="https://youtu.be/g4TGzaBER1U">see the same content in this video</a>.</p>

<h2 id="error-message">Error message</h2>

<p>If you attempt to use File Explorer to delete a file that is in use by another process, you will get an error message similar to:</p>

<blockquote>
  <p>File In Use</p>

  <p>The action can’t be completed because the file is open in [process name]</p>

  <p>Close the file and try again.</p>
</blockquote>

<p><img src="/assets/Posts/2021-07-28-Find-which-Windows-process-has-file-in-use/FileInUseDeleteErrorMessageScreenshot.png" alt="File-in-use delete error message screenshot" /></p>

<p>If you attempt to delete the file in a different application, the wording of the error message may be different.</p>

<p>Similarly, if you attempt to modify a file that is in use by another process, you’ll likely get an error about not being able to write to the file because it’s in use or locked by another process.
Same thing if you try and rename or delete a directory that is in use by another process.
The exact wording of the error message will vary depending on the application being used to update / delete the file / directory.</p>

<h3 id="reproduce-the-above-error-message">Reproduce the above error message</h3>

<p>To reproduce the above error, I used the following PowerShell script and then attempted to delete the file in File Explorer:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$tempFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Temp\FileLock\Temp.txt"</span><span class="w">
</span><span class="nv">$tempFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.File</span><span class="p">]::</span><span class="n">Open</span><span class="p">(</span><span class="nv">$tempFilePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.FileMode</span><span class="p">]::</span><span class="n">OpenOrCreate</span><span class="p">)</span><span class="w">
</span><span class="c"># $tempFile.Dispose() # Close file when done reading/writing</span><span class="w">
</span></code></pre></div></div>

<p>Normally you would close the file using the Dispose method when done reading / writing, but I commented that out so the file lock would remain in place.</p>

<h2 id="finding-which-process-is-using-the-file--directory">Finding which process is using the file / directory</h2>

<p>Windows has a built-in way to see which processes are actively using a file or directory, using the <code class="language-plaintext highlighter-rouge">Resource Monitor</code>.</p>

<p>To open the <code class="language-plaintext highlighter-rouge">Resource Monitor</code>, you can simply hit the <kbd>Windows Key</kbd> and search for it.
It can also be found in the Task Manager’s <code class="language-plaintext highlighter-rouge">Performance</code> tab.</p>

<p><img src="/assets/Posts/2021-07-28-Find-which-Windows-process-has-file-in-use/TaskManagerResourceMonitorScreenshot.png" alt="Task Manager Resource Monitor screenshot" /></p>

<p>Simply open the <code class="language-plaintext highlighter-rouge">Resource Monitor</code>, go to the <code class="language-plaintext highlighter-rouge">CPU</code> tab, and enter the file or directory path you want to check into the <code class="language-plaintext highlighter-rouge">Search Handles</code> box, making sure to not include quotes around the path.</p>

<p><img src="/assets/Posts/2021-07-28-Find-which-Windows-process-has-file-in-use/ResourceMonitorEnterPathScreenshot.png" alt="Resource Monitor enter path screenshot" /></p>

<p>If a process has a handle to the file or directory, you should see results:</p>

<p><img src="/assets/Posts/2021-07-28-Find-which-Windows-process-has-file-in-use/ResourceMonitorProcessFoundScreenshot.png" alt="Resource Monitor process found screenshot" />]</p>

<p>Note that it shows you the PID (Process ID) of the process that has the handle to the file or directory.
This is important because you may have several processes with the same name in Task Manager, so you can use the PID to identify which process is using the file or directory.</p>

<p>From there you can use the <code class="language-plaintext highlighter-rouge">Task Manager</code> to find out more about the process, or simply right-click the process in the <code class="language-plaintext highlighter-rouge">Resource Monitor</code> window and select <code class="language-plaintext highlighter-rouge">End Process</code> to kill the process and release the lock on the file / directory.</p>

<p>Once that is done you should be able to modify or delete the file or directory.</p>

<p>Happy process hunting :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows" /><category term="Windows" /><category term="File" /><category term="Directory" /><category term="Lock" /><category term="In use" /><summary type="html"><![CDATA[When a file or directory is in use by a process (application), you will not be able to delete or modify the file / directory. Unfortunately it’s not always obvious which process is using the file. Luckily, there is an easy way to find out without the need of 3rd party tools, which is what we’ll look at in this post.]]></summary></entry><entry><title type="html">Master the Windows copy-paste clipboard</title><link href="https://blog.danskingdom.com/Master-the-Windows-copy-paste-clipboard/" rel="alternate" type="text/html" title="Master the Windows copy-paste clipboard" /><published>2021-07-17T00:00:00+00:00</published><updated>2021-07-17T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Master-the-Windows-copy-paste-clipboard</id><content type="html" xml:base="https://blog.danskingdom.com/Master-the-Windows-copy-paste-clipboard/"><![CDATA[<p>Copying and pasting is a common task that pretty much everyone does.
Becoming more efficient with it is a great way to improve your productivity.</p>

<p>In this post we’ll cover:</p>

<ul>
  <li>What is the Windows clipboard.</li>
  <li>The basics of using the Windows clipboard for copying and pasting (<a href="https://www.youtube.com/watch?v=sZsSSVS7gY0">video</a>).</li>
  <li>Look at new clipboard features introduced in Windows 10 (<a href="https://www.youtube.com/watch?v=Ob2FHVPyXhc">video</a>).</li>
  <li>Be more productive by using free 3rd party tools that take the clipboard to the next level (<a href="https://www.youtube.com/watch?v=bBvKvJfWw2c">video</a>).</li>
</ul>

<p>I’ve provided links to videos that cover these topics as well if you prefer to learn that way.</p>

<h2 id="what-is-the-clipboard">What is the clipboard</h2>

<p>The Windows clipboard is simply a place that you can <code class="language-plaintext highlighter-rouge">copy</code> information to, and <code class="language-plaintext highlighter-rouge">paste</code> to get it out again.</p>

<p>You’ve likely used the clipboard before, but may not have known that it was called the clipboard.
Any time you copy or cut, you are placing information from an application on the clipboard; this is known as a <code class="language-plaintext highlighter-rouge">clip</code>.
When you paste, you’re copying that information (i.e. clip) from the clipboard back out into an application.</p>

<p>You can copy pretty much any type of information into the clipboard, including text, images, and files/directories.</p>

<h2 id="windows-clipboard-basics">Windows clipboard basics</h2>

<p>The basic clipboard operations are:</p>

<ul>
  <li>Copy: Copy information from an application onto the clipboard.</li>
  <li>Cut: Copy information from an application onto the clipboard, and also remove that information from the application.</li>
  <li>Paste: Copy information from the clipboard into an application. The information stays in the clipboard, allowing you to paste it multiple times if needed.</li>
</ul>

<p>You can typically find these operations in an application’s context menu by selecting some text/images/files and right-clicking with the mouse.</p>

<p>A much quicker method is to use the keyboard shortcuts.</p>

<ul>
  <li>Copy: <kbd>Ctrl</kbd> + <kbd>C</kbd></li>
  <li>Cut: <kbd>Ctrl</kbd> + <kbd>X</kbd></li>
  <li>Paste: <kbd>Ctrl</kbd> + <kbd>V</kbd></li>
</ul>

<p>Sometimes an application may not have these operations in their mouse context menu, or may not even provide a context menu at all, but the keyboard shortcuts work in most cases.</p>

<h2 id="windows-10-clipboard-enhancements">Windows 10 clipboard enhancements</h2>

<p>Before Windows 10, there was no way of seeing what was in the clipboard, if anything, besides to try pasting it.
Also, the clipboard could only hold the last information that was copied; i.e. one clip.</p>

<p>Windows 10 introduced a new feature called the Windows Clipboard History.
This feature includes the following benefits:</p>

<ul>
  <li>Can hold the last 25 clips copied. Older clips roll off the clipboard and cannot be accessed any more.</li>
  <li>Allows you to visualize the clipboard and pick which clip you want to paste.</li>
  <li>Allows you to “Pin” clips so they don’t roll off the clipboard.</li>
  <li>Allows you to share your clipboard between other devices.</li>
</ul>

<p>To access the Clipboard History feature simply use <kbd>Windows Key</kbd> + <kbd>V</kbd>.
If the Clipboard History feature is not enabled, the window will prompt you to turn it on.</p>

<p>Go ahead, press <kbd>Windows Key</kbd> + <kbd>V</kbd> right now. I’ll wait…..</p>

<p>Once enabled, just start copying things.
If you want to paste the last thing you copied, use <kbd>Ctrl</kbd> + <kbd>V</kbd> as usual.
If you want to paste something you copied earlier though, use <kbd>Windows Key</kbd> + <kbd>V</kbd> and the Clipboard History window will appear and show you the last clips you copied.
From there, choose the clip that you want and it will be pasted into your application.
If you click away from the Clipboard History window, it will close without pasting anything.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/Windows10ClipboardHistoryScreenshot.png" alt="Windows 10 Clipboard History Screenshot" /></p>

<p>Nice.</p>

<p>And, it’s built right into Windows 10, so there’s no need to download or install anything extra; it’s just there.</p>

<p>You can access the Clipboard History settings by hitting the <kbd>Windows Key</kbd> and searching for <code class="language-plaintext highlighter-rouge">Clipboard settings</code>.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/Windows10ClipboardSettingsScreenshot.png" alt="Windows 10 Clipboard Settings" /></p>

<h3 id="whats-lacking-in-the-windows-10-clipboard-history">What’s lacking in the Windows 10 Clipboard History</h3>

<p>While it’s definitely an improvement, the Clipboard History feature is far from perfect.
Here’s some downsides:</p>

<ul>
  <li>Only holds the last 25 copied clips, which isn’t enough in my opinion.</li>
  <li>All 25 clips are cleared every time you restart your computer.</li>
  <li>Cannot search in the Clipboard History.</li>
  <li>Images don’t always show up; seems to depend how they are copied or which application they were copied from.</li>
  <li>The Clipboard History window cannot be moved or resized.</li>
</ul>

<p>Hopefully these limitations will be addressed in later Windows updates, but at the moment they exist (I’m currently using Windows 10 version 20H2).</p>

<h2 id="free-3rd-party-clipboard-manager-apps-that-are-way-better">Free 3rd party clipboard manager apps that are way better</h2>

<p>While the Windows 10 Clipboard History was only introduced in the past couple of years, 3rd party apps have been doing similar things for over 10 years. This means they’ve had a lot of time to introduce great features that really enhance the clipboard, such as:</p>

<ul>
  <li>Configuring how many clips to save in the clipboard manager. e.g. thousands.</li>
  <li>Searching for clips in the clipboard manager.</li>
  <li>Favourite clips for easy access later.</li>
  <li>Pasting clips as plain text to remove formatting.</li>
  <li>Customizing additional keyboard shortcuts for quick access to additional functionality.</li>
</ul>

<p>There are plenty of clipboard managers to choose from; you can find some lists <a href="https://www.maketecheasier.com/five-free-clipboard-managers-for-windows">here</a> and <a href="https://www.slant.co/topics/4034/~clipboard-managers-for-windows">here</a>.
<a href="https://sourceforge.net/p/clip-angel/wiki/Description/">The ClipAngel wiki</a> has a nice comparison table of a few popular ones.</p>

<h2 id="ditto-clipboard-manager">Ditto clipboard manager</h2>

<p>While there are many clipboard managers out there, my favourite is the <a href="https://ditto-cp.sourceforge.io">Ditto Clipboard Manager</a>, which is free and can be installed from their website or <a href="https://www.microsoft.com/en-us/store/p/ditto-cp/9nblggh3zbjq">from the Microsoft store</a>.
I like Ditto because it provides a minimalistic user interface, while still providing a ton of options so you can configure it just the way you like.</p>

<p>You can open Ditto by using a customizable keyboard shortcut (default is <kbd>Ctrl</kbd> + <kbd>`</kbd>), by double-clicking the tray icon (which looks like double quotes on a blue square <img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoTrayIconImage.png" alt="Ditto Tray Icon Context Menu Image" />), or by right-clicking the tray icon and selecting <code class="language-plaintext highlighter-rouge">Show Quick Paste</code>.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoTrayIconContextMenuScreenshot.png" alt="Ditto Tray Icon Context Menu Screenshot" /></p>

<p>Here’s a screenshot of what my Ditto window currently looks like:</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoMainWindowScreenshot.png" alt="Ditto Main Window Screenshot" /></p>

<p>You can see that it displays previews for both text and images.
If you hover your mouse over a clip you’ll get a larger preview of that clip, as well as some extra information, such as when it was copied to the clipboard.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoMainWindowTooltipScreenshot.png" alt="Ditto Tooltip Screenshot" /></p>

<p>If you want to see the entire text, or the image in it’s original size, you can right-click on the clip and choose <code class="language-plaintext highlighter-rouge">View Full Description</code>, or press <kbd>F3</kbd>.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoMainWindowFullImagePreviewScreenshot.png" alt="Ditto Full Image Preview Screenshot" /></p>

<p>The window can be moved around and is resizable, so if you want to see more or less clips, you can resize the window.</p>

<p>The best feature though is the ability to search for clips.
Just open the Ditto window and start typing.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoMainWindowSearchScreenshot.png" alt="Ditto Search Screenshot" /></p>

<p>I use this feature all the time, typically to find something that I copied earlier that day or a few days ago.
It’s even been a life saver at recovering something that I copied months ago.</p>

<h3 id="ditto-options">Ditto options</h3>

<p>Ditto has so many options, I’m not going to even try to list them all here.
I will however show you the ones that I feel are noteworthy, and ones that I change from their default values.
You can access the Options window from the <code class="language-plaintext highlighter-rouge">...</code> on the bottom-right of the main window, or by right-clicking the tray icon and selecting <code class="language-plaintext highlighter-rouge">Options</code>.</p>

<h4 id="general-tab">General tab</h4>

<p>On the <code class="language-plaintext highlighter-rouge">General</code> tab, the options of note are:</p>

<ul>
  <li><strong>Start Ditto on System Startup</strong>: Launch Ditto automatically so you don’t have to.</li>
  <li><strong>Maximum Number of Saved Copies</strong>: The maximum number of clips to save in the clipboard manager. Use this to ensure the database doesn’t grow too large. I typically increase this quite a bit as I like being able to find clips from months ago.
    <ul>
      <li>You can check the <code class="language-plaintext highlighter-rouge">Database Path</code> file to see how large the database is. Depending on how large you set this, the database may consume GB of disk space.</li>
    </ul>
  </li>
  <li><strong>Theme</strong>: Ditto supports both light and dark themes.</li>
  <li><strong>Popup Position</strong>: Do you want the window to appear where your typing cursor is, where your mouse is, or where you last moved it to.</li>
</ul>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoOptionsGeneralTabScreenshot.png" alt="Ditto Options General Tab Screenshot" /></p>

<p>If you click on the <code class="language-plaintext highlighter-rouge">Advanced</code> button, you’ll find a lot more options. One that I typically change is:</p>

<ul>
  <li><strong>Paste Clip in active window after selection</strong>: Default is true, but I prefer this as false so that I have to manually use <kbd>Ctrl</kbd> + <kbd>V</kbd> to paste into an application.</li>
</ul>

<p>In there you’ll also find buttons for running custom scripts whenever you copy or paste.
I haven’t played around with it yet, but it looks interesting and there are links to a wiki page with examples.</p>

<h4 id="keyboard-shortcuts-tab">Keyboard Shortcuts tab</h4>

<p>On the <code class="language-plaintext highlighter-rouge">Keyboard Shortcuts</code> tab the options of note are:</p>

<ul>
  <li><strong>Activate Ditto</strong>: The keyboard shortcut to open Ditto. The default is <kbd>Ctrl</kbd> + <kbd>`</kbd>, but I’ve modified mine to be <kbd>Alt</kbd> + <kbd>`</kbd> because I use <kbd>Ctrl</kbd> + <kbd>`</kbd> to open the terminal in Visual Studio and Visual Studio Code.</li>
  <li><strong>Text Only Paste</strong>: This is a great one, as it will paste the clip with all formatting removed, including font, color, size, hyperlinks, etc. No more copying to Notepad to remove formatting!</li>
</ul>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoOptionsKeyboardShortcutsTabScreenshot.png" alt="Ditto Options Keyboard Shortcuts Tab Screenshot" /></p>

<h4 id="quick-paste-keyboard-tab">Quick Paste Keyboard tab</h4>

<p>On the <code class="language-plaintext highlighter-rouge">Quick Paste Keyboard</code> tab you’ll find tons of configurable hotkeys.
The ones I want to mention are <code class="language-plaintext highlighter-rouge">Paste Position 1</code> - <code class="language-plaintext highlighter-rouge">Paste Position 10</code>, as these allow you to quickly select one of the first 10 clips using the keyboard when you’re in Ditto.
This can save you from reaching for the mouse and having to click on the clip you want to select.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/DittoOptionsQuickPasteKeyboardTabScreenshot.png" alt="Ditto Options Quick Paste Keyboard Tab Screenshot" /></p>

<h2 id="clipangel-clipboard-manager">ClipAngel clipboard manager</h2>

<p>Another great free clipboard manager is <a href="https://sourceforge.net/projects/clip-angel">ClipAngel</a>.
ClipAngel is very comparable to Ditto and provides many of the same features, such as being able to search for clips, and pasting clips as plain text.</p>

<p>This is what ClipAngel looks like:</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/ClipAngelMainWindowScreenshot.png" alt="ClipAngel Main Window Screenshot" /></p>

<p>The default keyboard shortcut to open ClipAngel is <kbd>Alt</kbd> + <kbd>V</kbd>, and that can be changed if you like.
You can also double click on the tray icon to open ClipAngel.</p>

<p>As you can see, ClipAngel shows quite a bit more things in it’s user interface, including a preview window of the selected clip.
The preview window is nice when wanting to quickly view images or very long text clips.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/ClipAngelTextFilterOptionsScreenshot.png" alt="ClipAngel Text Filter Options Screenshot" /></p>

<p>Searching works as you would expect; simply open ClipAngel and start typing.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/ClipAngelSearchScreenshot.png" alt="ClipAngel Search Screenshot" /></p>

<p>ClipAngel provides a few nice features that Ditto does not, such as showing which application a clip was copied from, and the ability to filter clips by various criteria.
For example, you can have the list show only image clips, clips that contain an email address, or clips created between a certain date range.
Those additional filter options are great for when you can’t remember specific text from the clip that you’re searching for.</p>

<p><img src="/assets/Posts/2021-07-17-Master-the-Windows-copy-paste-clipboard/ClipAngelListFilterOptionsScreenshot.png" alt="ClipAngel List Filter Options Screenshot" /></p>

<p>I personally prefer Ditto for it’s minimalistic user interface and ton of configurable options, but you may prefer ClipAngel.</p>

<h2 id="conclusion">Conclusion</h2>

<p>In this post we looked at what the clipboard is, it’s basic operations, how to leverage the Clipboard History feature built into Windows 10, and looked at a couple free 3rd party clipboard managers that can greatly improve the clipboard experience.</p>

<p>We looked at the Ditto and ClipAngel clipboard managers, both of which are free and still under active development, so their feature sets may continue to expand and improve.
There are other options out there, but of the ones I tried these 2 were my favourite and are the ones I’d recommend.</p>

<p>Do you use a different clipboard manager that you think is better?
Let me know in the comments!</p>

<p>If you’re not using a clipboard manager yet, I hope you’ll consider it, and that it makes you more productive.</p>

<p>Happy copy-pasting :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows" /><category term="Productivity" /><category term="Windows" /><category term="Productivity" /><summary type="html"><![CDATA[Copying and pasting is a common task that pretty much everyone does. Becoming more efficient with it is a great way to improve your productivity.]]></summary></entry><entry><title type="html">Reasons to use both a local and global editorconfig file</title><link href="https://blog.danskingdom.com/Reasons-to-use-both-a-local-and-global-editorconfig-file/" rel="alternate" type="text/html" title="Reasons to use both a local and global editorconfig file" /><published>2021-01-13T00:00:00+00:00</published><updated>2021-01-13T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Reasons-to-use-both-a-local-and-global-editorconfig-file</id><content type="html" xml:base="https://blog.danskingdom.com/Reasons-to-use-both-a-local-and-global-editorconfig-file/"><![CDATA[<h2 id="what-is-editorconfig">What is EditorConfig</h2>

<p><img alt="Editor icons" src="/assets/Posts/2021-01-13-Reasons-to-use-both-a-local-and-global-editorconfig-file/EditorIcons.png" class="right" /></p>

<p><a href="https://editorconfig.org">EditorConfig</a> is a project that aims to define common editor configuration outside of your editor.
The settings are instead stored in a <code class="language-plaintext highlighter-rouge">.editorconfig</code> file, which can be committed to your source control repository, or live outside of it.
These settings include things like if tabs or spaces should be preferred, if whitespace should be trimmed off every line, what file encoding to use, and more; <a href="https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties">see the full list here</a>.</p>

<p>A few years ago EditorConfig started to become very popular and receive mass adoption.
Today, <a href="https://editorconfig.org/#download">pretty much every major editor and IDE</a> either natively support EditorConfig, or has a plugin for it.
Some even extend the native EditorConfig property list, such as how <a href="https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options#example-editorconfig-file">Visual Studio allows you to specify .Net coding conventions in it</a>.</p>

<p>While EditorConfig hasn’t ended the infamous tabs vs. spaces debate, it has at least made it easy to achieve consistency within a repository or project.
<a href="https://www.hanselman.com/blog/tabs-vs-spaces-a-peaceful-resolution-with-editorconfig-in-visual-studio-plus-net-extensions">Scott Hanselman</a> and <a href="https://devblog.dymel.pl/2018/01/29/tabs-vs-spaces-editorconfig/">others</a> have blogged about this.
Not having to worry about which repositories use tabs and which use spaces, what file encoding to use, which end-of-line character to use, etc. and just have it automatically use the right settings for the current project is wonderful.</p>

<h2 id="the-problem">The problem</h2>

<p><img alt="Code" src="/assets/Posts/2021-01-13-Reasons-to-use-both-a-local-and-global-editorconfig-file/Code.jpg" class="left" /></p>

<p>That said, there’s still one issue I come across, which is that teams tend to put presentation-only properties in the .editorconfig file that gets committed to source control.
The biggest offender is the <code class="language-plaintext highlighter-rouge">indent_size</code> property used with an <code class="language-plaintext highlighter-rouge">indent_style</code> of <code class="language-plaintext highlighter-rouge">tabs</code>, as well as the <code class="language-plaintext highlighter-rouge">tab_width</code> property.
This property does not affect the physical contents of the file, and is solely a personal preference presentation setting.
Some people might like their tabs represented as 4 spaces, while others like it as 2 to save on horizontal space, while others might prefer 8 for accessibility reasons.</p>

<p>As mentioned earlier, some editors have extended the list of EditorConfig properties.
For example, Visual Studio allows you to specify if Visual Studio should suggest transforming a simple one-line method into an expression bodied method.
Some users may find that helpful, while others may find it annoying and want to change it from a suggestion to being silent (so Visual Studio doesn’t underline it with a blue squiggle).
If I add that property to my .editorconfig file and set it to silent, other team members may miss out on a feature they love.</p>

<h2 id="my-solution">My solution</h2>

<p>So how do we solve this problem?
The answer is to use 2 .editorconfig files.
A local .editorconfig file that contains team settings and gets committed to source control in your repository, and a global .editorconfig file for personal settings that lives in a directory above all of your repositories, outside of source control (you can still keep it in source control <em>somewhere</em>, just not in every repository).</p>

<p>The global .editorconfig file:</p>

<ul>
  <li>Lives in a directory above all of your repositories.</li>
  <li>Can contain any properties you like; both presentation-only properties and properties that modify file contents.</li>
</ul>

<p>The local .editorconfig file:</p>

<ul>
  <li>Gets committed to source control in your repository.</li>
  <li><strong>Should not contain any presentation-only properties</strong>, such as tab width; only include properties that affect actual file contents, and that you want enforced in the repository.</li>
  <li>Should have <code class="language-plaintext highlighter-rouge">root = false</code> defined so that presentation-only (and other) properties can be inherited from the global .editorconfig file.</li>
</ul>

<p>Here is an example of my personal global .editorconfig file <a href="https://gist.github.com/deadlydog/f83de31269f6f9982d26cfbd70bbf50f">(gist)</a>:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This .editorconfig file should live outside of all repositories (and thus not be committed to source control) in
# a parent directory, as it includes personal preference presentation settings, like a tab's `indent_size`.
# v1.2
</span>
<span class="py">root</span> <span class="p">=</span> <span class="s">true</span>

<span class="nn">[*]</span>
<span class="py">indent_style</span> <span class="p">=</span> <span class="s">tab</span>
<span class="py">end_of_line</span> <span class="p">=</span> <span class="s">crlf</span>
<span class="py">trim_trailing_whitespace</span> <span class="p">=</span> <span class="s">true</span>
<span class="py">insert_final_newline</span> <span class="p">=</span> <span class="s">true</span>
<span class="py">indent_size</span> <span class="p">=</span> <span class="s">4</span>

<span class="nn">[*.{html,xml,config,json}]</span>
<span class="py">indent_size</span> <span class="p">=</span> <span class="s">2</span>

<span class="nn">[*.{md,psd1,pp,yml,yaml}]</span>
<span class="py">indent_style</span> <span class="p">=</span> <span class="s">space</span>
<span class="py">indent_size</span> <span class="p">=</span> <span class="s">2</span>
</code></pre></div></div>

<p>And of my default local .editorconfig file <a href="https://gist.github.com/deadlydog/bd000162e85c155b243a712c16f7411c">(gist)</a> that I drop in my git repositories:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This file should only include settings that affect the physical contents of the file, not just how it appears in an editor.
# Do not include personal preference presentation settings like a tab's `indent_size` in this file; those should be specified
# in a parent .editorconfig file outside of the repository.
# v1.4
</span>
<span class="c"># Ensure that personal preference presentation settings can be inherited from parent .editorconfig files.
</span><span class="py">root</span> <span class="p">=</span> <span class="s">false</span>

<span class="nn">[*]</span>
<span class="py">indent_style</span> <span class="p">=</span> <span class="s">tab</span>
<span class="py">end_of_line</span> <span class="p">=</span> <span class="s">crlf</span>
<span class="py">trim_trailing_whitespace</span> <span class="p">=</span> <span class="s">true</span>
<span class="py">insert_final_newline</span> <span class="p">=</span> <span class="s">true</span>

<span class="nn">[*.{md,psd1,pp,yml,yaml}]</span>
<span class="py">indent_style</span> <span class="p">=</span> <span class="s">space</span>
<span class="py">indent_size</span> <span class="p">=</span> <span class="s">2</span>
</code></pre></div></div>

<p>Those gist links essentially act as the source control for my personal .editorconfig files.</p>

<p>Notice that the local .editorconfig file has <code class="language-plaintext highlighter-rouge">root = false</code> defined, and does not include an <code class="language-plaintext highlighter-rouge">indent_size</code> when <code class="language-plaintext highlighter-rouge">indent_style = tab</code>, while the global .editorconfig file does.</p>

<p>You may also notice that aside from these 2 properties, the files are very similar.
That is because these are <em>my files</em> and they reflect <em>my preferences</em>.
I might clone an open source git repo, or one that a different team in my office maintains, and their local .editorconfig file may look very different from my global one, but I can be sure that their local .editorconfig file settings will be used instead of my global ones.</p>

<h3 id="why-this-works">Why this works</h3>

<p>EditorConfig works using an inheritance model.
That is:</p>

<blockquote>
  <p>When opening a file, EditorConfig plugins look for a file named .editorconfig in the directory of the opened file and in every parent directory.
A search for .editorconfig files will stop if the root filepath is reached or an EditorConfig file with root=true is found.</p>

  <p>EditorConfig files are read top to bottom and the most recent rules found take precedence.
Properties from matching EditorConfig sections are applied in the order they were read, so properties in closer files take precedence.</p>
</blockquote>

<p><img alt="Globe" src="/assets/Posts/2021-01-13-Reasons-to-use-both-a-local-and-global-editorconfig-file/Globe.jpg" class="right" /></p>

<p>This means that properties found in an .editorconfig file closer to the file will override ones found further away from the file.</p>

<p>So if you wanted, you could place your global .editorconfig file at <code class="language-plaintext highlighter-rouge">C:\.editorconfig</code> and it would apply to any file you open in your editor, whether they are part of a git repository or not.
Any properties defined in your repository’s local .editorconfig file will override the global ones.</p>

<p>I wouldn’t actually recommend putting your global .editorconfig file at <code class="language-plaintext highlighter-rouge">C:\.editorconfig</code>, as you may not want your editor to auto-format files you open in <code class="language-plaintext highlighter-rouge">C:\Windows</code> or <code class="language-plaintext highlighter-rouge">C:\Program Files</code>.</p>

<p>I keep all of my git repositories under <code class="language-plaintext highlighter-rouge">C:\dev\Git</code>, so my global .editorconfig file lives in that directory.</p>

<p><img src="/assets/Posts/2021-01-13-Reasons-to-use-both-a-local-and-global-editorconfig-file/EditorconfigFilesInFileExplorer.png" alt="Editorconfig files in File Explorer" /></p>

<h2 id="the-benefits">The benefits</h2>

<p>Benefits of using a global .editorconfig file include:</p>

<ul>
  <li>Everyone’s personal preference presentation-only properties can be respected, so long as those properties aren’t overridden in the local .editorconfig file (if they are, you can likely remove them).</li>
  <li>When working in a repository that does not have an .editorconfig file, I still get all of <em>my</em> personal properties applied.</li>
  <li>Depending on which language I’m working in, I use different editors / IDEs.
Because I always have <em>at least</em> my global .editorconfig file being applied, I no longer have to worry about configuring each editor the same way for all of the different file types; the .editorconfig file handles that for me.</li>
</ul>

<p><img src="/assets/Posts/2021-01-13-Reasons-to-use-both-a-local-and-global-editorconfig-file/Hooray.gif" alt="Hooray" /></p>

<h2 id="conclusion">Conclusion</h2>

<p>I’ve been using this strategy of both a global and local .editorconfig file for a couple years now and have found it works well for me.
I haven’t read about it or seen it elsewhere though, so I thought I’d share.</p>

<p>What are your thoughts on this approach?
Do you think you’ll try it?
If you do, let me know how you find it.
Leave me a comment below.</p>

<p>Happy editing :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="Editor" /><category term="IDE" /><category term="Productivity" /><category term="Editor" /><category term="IDE" /><category term="EditorConfig" /><summary type="html"><![CDATA[What is EditorConfig]]></summary></entry><entry><title type="html">Customize Git config based on the repository path</title><link href="https://blog.danskingdom.com/Customize-Git-config-based-on-the-repository-path/" rel="alternate" type="text/html" title="Customize Git config based on the repository path" /><published>2020-11-08T00:00:00+00:00</published><updated>2020-11-08T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Customize-Git-config-based-on-the-repository-path</id><content type="html" xml:base="https://blog.danskingdom.com/Customize-Git-config-based-on-the-repository-path/"><![CDATA[<p>Like many people, I use my laptop for both personal and work projects.
One thing I noticed a while back was that the commits to my work Git repos were using my personal username, <code class="language-plaintext highlighter-rouge">deadlydog</code>, and email address.
That doesn’t look very professional, so I got excited when I saw <a href="https://twitter.com/terrajobst/status/1324481475652190208">this tweet from Immo Landwerth</a> about how he solved the problem.</p>

<p>His solution was to run a custom command in every Git repo that would add the appropriate variables to the repo’s Git config.
While that works, it’s recurring manual work that I wanted to avoid.
Fortunately, awesome community members replied to his tweet saying that Git conditional includes could be used instead, so I investigated it, and that’s what I’m going to show here.</p>

<h2 id="using-git-includes">Using Git includes</h2>

<p>Essentially <a href="https://git-scm.com/docs/git-config#_includes">Git includes</a> allow you to reference another file from your <code class="language-plaintext highlighter-rouge">.gitconfig</code> file.
When you do this, it acts as if whatever text is in the included file was present in the .gitconfig file.</p>

<p>So if your <a href="https://git-scm.com/docs/git-config#FILES">global .gitconfig file</a> contained the text:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[user]</span>
  <span class="py">name</span> <span class="p">=</span> <span class="s">deadlydog</span>
  <span class="py">email</span> <span class="p">=</span> <span class="s">deadlydog@hotmail.com</span>

<span class="nn">[include]</span>
  <span class="py">path</span> <span class="p">=</span> <span class="s">C:/Git/WorkProjects/Work.gitconfig</span>
</code></pre></div></div>

<p>And “Work.gitconfig” contained the text:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[user]</span>
  <span class="py">name</span> <span class="p">=</span> <span class="s">Daniel Schroeder</span>
  <span class="py">email</span> <span class="p">=</span> <span class="s">DanielSchroeder@Work.com</span>
</code></pre></div></div>

<p>Then Git would interpret the final configuration to be:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[user]</span>
  <span class="py">name</span> <span class="p">=</span> <span class="s">deadlydog</span>
  <span class="py">email</span> <span class="p">=</span> <span class="s">deadlydog@hotmail.com</span>

<span class="nn">[user]</span>
  <span class="py">name</span> <span class="p">=</span> <span class="s">Daniel Schroeder</span>
  <span class="py">email</span> <span class="p">=</span> <span class="s">DanielSchroeder@Work.com</span>
</code></pre></div></div>

<p>In Git if you define the same settings twice, whichever one was defined last would win and be used, so in this case Git would mark the commits as being created by “Daniel Schroeder”, not “deadlydog”.</p>

<p>You can typically find your global .gitconfig file in your user directory.
e.g. “C:\Users\Dan.Schroeder\.gitconfig”</p>

<h2 id="using-git-conditional-includes">Using Git conditional includes</h2>

<p>The last piece of the puzzle is to only include that external configuration file if it’s actually a work project, not a personal project.</p>

<p>Git allows <a href="https://git-scm.com/docs/git-config#_conditional_includes">conditional includes</a> on both directory paths and branch names.
This means I can accomplish our goal by simply putting all of my work projects in a different directory than my personal projects.
I typically put all of my personal git repositories in C:\Git, and my work git repositories in C:\Git\WorkProjects.</p>

<p>Here is what the final global <code class="language-plaintext highlighter-rouge">.gitconfig</code> code looks like:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[user]</span>
  <span class="py">name</span> <span class="p">=</span> <span class="s">deadlydog</span>
  <span class="py">email</span> <span class="p">=</span> <span class="s">deadlydog@hotmail.com</span>

<span class="c"># ... all of the other .gitconfig settings.
</span>
<span class="nn">[includeIf "gitdir:C:/Git/WorkProjects/**"]</span>
  <span class="py">path</span> <span class="p">=</span> <span class="s">C:/Git/WorkProjects/Work.gitconfig</span>
</code></pre></div></div>

<p>Now if I’m working in a Git repository under the “C:\Git\WorkProjects\” directory then Work.gitconfig will get included, otherwise it won’t.</p>

<p>There’s a few things to note here:</p>

<ol>
  <li>You will want to add your includes to the bottom of your .gitconfig file.
This will ensure the included file settings always take precedence over what is directly in the .gitconfig file, since the last setting value defined in the .gitconfig file is what gets used.</li>
  <li>If you read <a href="https://git-scm.com/docs/git-config#_conditional_includes">the conditional include docs</a>, it mentions that if a path ends with a trailing slash you don’t need to include the <code class="language-plaintext highlighter-rouge">**</code> wildcards.
I prefer to include them to make it obvious that a wildcard match is happening, but using <code class="language-plaintext highlighter-rouge">"gitdir:C:/Git/WorkProjects/"</code> works the same.</li>
  <li>You can call the include file whatever you like.
Here I called it “Work.gitconfig”, but “.gitconfig”, or “GitSettings.inc” would work fine too.</li>
  <li>I’m running Windows, but in my .gitconfig file I use forward slashes for the directory paths.
Backslashes work, but need to be escaped, so you could use double backslashes if you prefer. e.g. C:\\Git\\WorkProjects\\.</li>
</ol>

<h3 id="use-relative-file-paths">Use relative file paths</h3>

<p>In the example above, I’ve placed the “Work.gitconfig” include file in the “WorkProjects” directory so it sits beside the Git repositories that it will apply to, and then referenced it by an absolute path in the .gitconfig file.
If you prefer, you could place the “Work.gitconfig” file in the same directory as your global .gitconfig file, and then reference it using a relative path, like this:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[includeIf "gitdir:C:/Git/WorkProjects/**"]</span>
  <span class="py">path</span> <span class="p">=</span> <span class="s">Work.gitconfig</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>By simply keeping your personal and work Git repositories in different directories, you can easily apply different Git settings.
Here I’ve shown overriding the user’s name and email, but you could use it for other things like preferring rebase instead of merge, which diff or merge tool to use, etc.
While I didn’t show it, you can also include different files based on what branch the Git repo is on; perhaps you want some different rules applied when on the main branch vs. a feature branch.</p>

<p>The advantages of using conditional includes in your global .gitconifg file instead of updating each individual repo’s config are:</p>

<ol>
  <li>No manual work is necessary to apply the config change to each repo.</li>
  <li>If I change settings in the include file, they take effect immediately in all repos.</li>
  <li>The changes are automatically applied to new repos placed under the gitdir directory.</li>
</ol>

<p>Lastly, after writing this up I came across <a href="https://www.motowilliams.com/conditional-includes-for-git-config">this similar blog post by Eric Williams</a>, so if you still have questions check that out, as well as <a href="https://git-scm.com/docs/git-config#_includes">the official git docs</a>.</p>

<p>Happy coding :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Git" /><category term="Productivity" /><category term="Git" /><category term="Productivity" /><summary type="html"><![CDATA[Like many people, I use my laptop for both personal and work projects. One thing I noticed a while back was that the commits to my work Git repos were using my personal username, deadlydog, and email address. That doesn’t look very professional, so I got excited when I saw this tweet from Immo Landwerth about how he solved the problem.]]></summary></entry><entry><title type="html">PowerShell intellisense on the command line</title><link href="https://blog.danskingdom.com/PowerShell-intellisense-on-the-command-line/" rel="alternate" type="text/html" title="PowerShell intellisense on the command line" /><published>2020-11-07T00:00:00+00:00</published><updated>2023-09-06T00:00:00+00:00</updated><id>https://blog.danskingdom.com/PowerShell-intellisense-on-the-command-line</id><content type="html" xml:base="https://blog.danskingdom.com/PowerShell-intellisense-on-the-command-line/"><![CDATA[<p>If you use PowerShell then this tip is for you; if you don’t already know it, it’s game changer!</p>

<p>You may already know that you can use <kbd>Tab</kbd> to autocomplete cmdlet names and parameters in the console.
If there’s more than one possible match, you can continue hitting <kbd>Tab</kbd> to cycle through them, and <kbd>Shift</kbd> + <kbd>Tab</kbd> to cycle in reverse order.</p>

<p><img src="/assets/Posts/2020-11-07-PowerShell-intellisense-on-the-command-line/PowerShellTabCompletion.gif" alt="Screencast showing tab completion" /></p>

<p>Things get even better by using <a href="https://github.com/PowerShell/PSReadLine">PSReadLine</a>.
Not only can you autocomplete cmdlets and parameters, but you can see all of the options available in a nice menu that you can navigate using the arrow keys.
You activate this menu by using <kbd>Ctrl</kbd> + <kbd>Space</kbd>.</p>

<p><img src="/assets/Posts/2020-11-07-PowerShell-intellisense-on-the-command-line/PowerShellMenuComplete.gif" alt="Screencast showing PSReadLine menu completion" /></p>

<p>Notice that when browsing the cmdlet parameters it also displays the parameter’s type.
e.g. It shows that <code class="language-plaintext highlighter-rouge">-Path</code> is a <code class="language-plaintext highlighter-rouge">[string[]]</code>, and <code class="language-plaintext highlighter-rouge">-Force</code> is a <code class="language-plaintext highlighter-rouge">[switch]</code>.
Very helpful!</p>

<p>This works for cmdlets, parameters, variables, methods, and more.
Basically anything that you can tab complete, you can also see in the menu.
For example:</p>

<ul>
  <li>Type <code class="language-plaintext highlighter-rouge">Get-Pro</code> then <kbd>Ctrl</kbd> + <kbd>Space</kbd> to see all cmdlets that start with <code class="language-plaintext highlighter-rouge">Get-Pro</code>.</li>
  <li>If you have the variables <code class="language-plaintext highlighter-rouge">$variable1</code> and <code class="language-plaintext highlighter-rouge">$variable2</code>, then type <code class="language-plaintext highlighter-rouge">$var</code> and <kbd>Ctrl</kbd> + <kbd>Space</kbd> to see both variables in the menu.</li>
  <li>Type <code class="language-plaintext highlighter-rouge">[string]::</code> and <kbd>Ctrl</kbd> + <kbd>Space</kbd> to see all the methods available on the <code class="language-plaintext highlighter-rouge">[string]</code> type.</li>
</ul>

<p>Anytime you want autocomplete, just hit <kbd>Ctrl</kbd> + <kbd>Space</kbd> and you’ll see all the options available.</p>

<p>If you’re on a Mac, then apparently the <kbd>Ctrl</kbd> + <kbd>Space</kbd> trigger won’t work; I don’t have a Mac, so I can’t confirm.
However, do not fret.
You can achieve the same functionality by adding this PowerShell code to <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles">your PowerShell profile</a>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-PSReadLineKeyHandler</span><span class="w"> </span><span class="nt">-Chord</span><span class="w"> </span><span class="nx">Shift</span><span class="o">+</span><span class="nx">Spacebar</span><span class="w"> </span><span class="nt">-Function</span><span class="w"> </span><span class="nx">MenuComplete</span><span class="w">
</span></code></pre></div></div>

<p>Now you should be able to activate the menu by using <kbd>Shift</kbd> + <kbd>Spacebar</kbd>.
Of course you can choose a different keyboard combination if you like, including overriding the default <kbd>Tab</kbd> behaviour with this, and this trick also works on Windows if you don’t like the default <kbd>Ctrl</kbd> + <kbd>Space</kbd> trigger.</p>

<p>A shout-out to Steve Lee of the PowerShell team for showing me the Menu Complete functionality <a href="https://twitter.com/Steve_MSFT/status/1324192341310124033">in this tweet</a>.</p>

<p>I hope you’ve found this information helpful.</p>

<p>Happy command lining :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Productivity" /><category term="Shortcuts" /><category term="PowerShell" /><category term="Productivity" /><category term="Shortcuts" /><summary type="html"><![CDATA[If you use PowerShell then this tip is for you; if you don’t already know it, it’s game changer!]]></summary></entry><entry><title type="html">Close those superfluous Zoom windows automatically</title><link href="https://blog.danskingdom.com/Close-those-superfluous-Zoom-windows-automatically/" rel="alternate" type="text/html" title="Close those superfluous Zoom windows automatically" /><published>2020-09-29T00:00:00+00:00</published><updated>2020-10-24T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Close-those-superfluous-Zoom-windows-automatically</id><content type="html" xml:base="https://blog.danskingdom.com/Close-those-superfluous-Zoom-windows-automatically/"><![CDATA[<h2 id="so-many-zoom-windows-to-close-every-day">So many Zoom windows to close every day</h2>

<p>We’ve been using Zoom for a while at my office, and I’ve noticed that anytime I join a meeting I need to go and close windows/tabs afterward.</p>

<p>Anytime you join a Zoom meeting via a link, whether via an Outlook meeting or URL sent in a DM, the flow is:</p>

<ol>
  <li>
    <p>Click the meeting URL link.</p>

    <p><img src="/assets/Posts/2020-09-29-Close-those-superfluous-Zoom-windows-automatically/Step1-OutlookZoomMeeting.png" alt="Outlook Zoom meeting invitation" /></p>
  </li>
  <li>
    <p>It opens a tab in your browser.</p>

    <p><img src="/assets/Posts/2020-09-29-Close-those-superfluous-Zoom-windows-automatically/Step2-ZoomMeetingBrowserUrl.png" alt="Browser Zoom meeting" /></p>
  </li>
  <li>
    <p>This then launches the Zoom app.</p>

    <p><img src="/assets/Posts/2020-09-29-Close-those-superfluous-Zoom-windows-automatically/Step3-ZoomAppWindow.png" alt="Zoom app" /></p>
  </li>
  <li>
    <p>Which then opens the actual Zoom meeting window.</p>

    <p><img src="/assets/Posts/2020-09-29-Close-those-superfluous-Zoom-windows-automatically/Step4-ZoomMeetingWindow.png" alt="Zoom meeting" /></p>
  </li>
</ol>

<p>Steps 2 and 3 are really unnecessary, and I find myself always having to go and close them after I’ve joined a meeting.
When you have multiple Zoom meetings every day, this routine of closing windows quickly gets old.</p>

<h2 id="the-code-to-close-the-zoom-windows-automatically">The code to close the Zoom windows automatically</h2>

<p>To help eliminate this tedious constant closing of windows and browser tabs (steps 2 and 3 above), I’ve created the following AutoHotkey script:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span><span class="n">SingleInstance</span><span class="p">,</span> <span class="n">Force</span>
<span class="err">#</span><span class="n">Persistent</span>

<span class="nf">CloseZoomWindowsAfterJoiningAMeeting</span><span class="p">()</span>
<span class="p">{</span>
  <span class="n">browserWindowTitleToMatch</span> <span class="p">:=</span> <span class="s">"Launch Meeting - Zoom"</span>

  <span class="p">;</span> <span class="n">If</span> <span class="n">a</span> <span class="n">browser</span> <span class="n">tab</span> <span class="n">to</span> <span class="k">join</span> <span class="n">a</span> <span class="n">meeting</span> <span class="n">exists</span><span class="p">.</span>
  <span class="n">IfWinExist</span><span class="p">,</span> <span class="p">%</span><span class="n">browserWindowTitleToMatch</span><span class="p">%</span>
  <span class="p">{</span>
    <span class="p">;</span> <span class="n">Put</span> <span class="n">the</span> <span class="n">browser</span> <span class="n">tab</span> <span class="n">window</span> <span class="k">in</span> <span class="n">focus</span><span class="p">.</span>
    <span class="n">WinActivate</span><span class="p">,</span> <span class="p">%</span><span class="n">browserWindowTitleToMatch</span><span class="p">%</span>

    <span class="p">;</span> <span class="n">Close</span> <span class="n">the</span> <span class="n">browser</span> <span class="nf">tab</span> <span class="p">(</span><span class="n">Ctrl</span> <span class="p">+</span> <span class="n">F4</span><span class="p">).</span>
    <span class="n">SendInput</span><span class="p">,</span> <span class="p">^{</span><span class="n">F4</span><span class="p">}</span>

    <span class="p">;</span> <span class="n">Wait</span> <span class="n">until</span> <span class="n">the</span> <span class="n">URL</span> <span class="n">opened</span> <span class="n">the</span> <span class="n">Zoom</span> <span class="n">app</span> <span class="n">to</span> <span class="k">join</span> <span class="n">the</span> <span class="n">meeting</span><span class="p">.</span>
    <span class="n">WinWait</span><span class="p">,</span> <span class="n">ahk_class</span> <span class="n">ZPPTMainFrmWndClassEx</span><span class="p">,,</span><span class="m">5</span>

    <span class="p">;</span> <span class="n">Close</span> <span class="n">the</span> <span class="n">Zoom</span> <span class="n">app</span><span class="p">.</span>
    <span class="n">IfWinExist</span><span class="p">,</span> <span class="n">ahk_class</span> <span class="n">ZPPTMainFrmWndClassEx</span>
    <span class="p">{</span>
      <span class="n">WinClose</span><span class="p">,</span> <span class="n">ahk_class</span> <span class="n">ZPPTMainFrmWndClassEx</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="n">SetTimer</span><span class="p">,</span> <span class="n">CloseZoomWindowsAfterJoiningAMeeting</span><span class="p">,</span> <span class="m">500</span>
</code></pre></div></div>

<blockquote>
  <p>Update: The code has been moved into <a href="https://github.com/deadlydog/CloseZoomWindowsAfterJoiningMeeting">this GitHub repository</a>, so check there for the latest version.</p>
</blockquote>

<p><strong>NOTE</strong>: This script assumes you’ve checked the box in your browser that says something to the effect of “Always open links of this type automatically”.
If you haven’t, then you’ll need to manually click the “Open” button in your browser every time; only this script will close the tab before you have a chance to click the Open button.</p>

<p>If you’re not familiar with AutoHotkey or how to use it, check out <a href="https://blog.danskingdom.com/Get-up-and-running-with-AutoHotkey">this post</a> to get familiar with it and how you can automate away many daily annoyances like this one.</p>

<h2 id="i-dont-know-what-that-gobbledygook-code-above-means">I don’t know what that gobbledygook code above means</h2>

<p>If programming code scares you, or you’re just feeling lazy, go ahead and <a href="https://github.com/deadlydog/CloseZoomWindowsAfterJoiningMeeting/releases">download the executable</a>.
It simply runs <a href="https://github.com/deadlydog/CloseZoomWindowsAfterJoiningMeeting/blob/master/src/CloseZoomWindowsAfterJoiningMeeting.ahk">the above script</a> for you without requiring you to install AutoHotkey or write any code.
Just double-click the executable to run it.</p>

<p>You’ll likely also want to check out <a href="https://blog.danskingdom.com/Get-up-and-running-with-AutoHotkey/#run-scripts-automatically-at-startup">how to run it automatically when you log into Windows</a> so that you don’t need to manually run it every time you restart your computer.</p>

<p>Happy Zooming!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Zoom" /><category term="AutoHotkey" /><category term="Productivity" /><category term="Zoom" /><category term="AutoHotkey" /><category term="Productivity" /><category term="AHK" /><summary type="html"><![CDATA[So many Zoom windows to close every day]]></summary></entry><entry><title type="html">Get up and running with AutoHotkey</title><link href="https://blog.danskingdom.com/Get-up-and-running-with-AutoHotkey/" rel="alternate" type="text/html" title="Get up and running with AutoHotkey" /><published>2020-09-28T00:00:00+00:00</published><updated>2020-10-15T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Get-up-and-running-with-AutoHotkey</id><content type="html" xml:base="https://blog.danskingdom.com/Get-up-and-running-with-AutoHotkey/"><![CDATA[<p>AutoHotkey (AHK) is an amazing programming language for automating tasks and keystrokes in Windows.
In this post we’ll look at how you can get your first AHK script up and running, and other configurations and processes you may want to adopt.
I’ve given some of these AHK setup instructions in previous posts, but figured they deserved their own dedicate post.</p>

<h2 id="install-ahk-and-run-your-first-script">Install AHK and run your first script</h2>

<p>To get up and running with AHK:</p>

<ol>
  <li><a href="https://www.autohotkey.com">Download and install</a> the current version of AutoHotkey.</li>
  <li>Create a new text file with the extension <code class="language-plaintext highlighter-rouge">.ahk</code>. e.g. MyHotkeys.ahk.</li>
  <li>
    <p>Open the text file in an editor, such as Notepad, and add some AHK code to the file, such as:</p>

    <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="err">#</span><span class="n">b</span><span class="p">::</span><span class="n">MsgBox</span><span class="p">,</span> <span class="s">"Hello World!"</span>
</code></pre></div>    </div>
  </li>
  <li>Save the file.</li>
  <li>Double-click the file to run it, or right-click it and choose <code class="language-plaintext highlighter-rouge">Run Script</code>.
  <img src="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/AhkContextMenu.png" alt="AHK file context menu" /></li>
</ol>

<p>You should see a new AutoHotkey icon appear in your system tray <img src="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/AhkSystemTrayIcon.png" alt="AHK system tray icon" />.
This is your running script.</p>

<p>If you used the line of code provided above, when you press <kbd>Windows key</kbd> + <kbd>b</kbd> you should see a message box pop up that says “Hello World!”.</p>

<p><img src="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/AhkHelloWorldMessageBox.png" alt="AHK Hello World message box" /></p>

<p>You can right-click the system tray icon and choose <code class="language-plaintext highlighter-rouge">Exit</code> to kill your script at any time.
If you edit the script, right-click the system tray icon and choose <code class="language-plaintext highlighter-rouge">Reload This Script</code> to restart the script with the changes applied.</p>

<p><img src="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/AhkSystemTrayIconContextMenu.png" alt="AHK system tray icon context menu" /></p>

<p>You did it!
You wrote and ran your first AHK script.</p>

<h2 id="editing-ahk-scripts">Editing AHK scripts</h2>

<p>While you can use any text editor to write your scripts, I would recommend either:</p>

<ol>
  <li><a href="https://code.visualstudio.com/download">Visual Studio Code</a> with the <a href="https://marketplace.visualstudio.com/items?itemName=cweijan.vscode-autohotkey-plus">AutoHotkey Plus extension</a> installed - A very nice experience that is constantly receiving updates.</li>
  <li><a href="https://ahk4.net/user/fincs/scite4ahk/">SciTE4AutoHotkey</a> - A great IDE, but hasn’t had an update since 2014.</li>
</ol>

<p>Both options provide syntax highlighting, intellisense, and debug support.
There are <a href="https://www.autohotkey.com/docs/commands/Edit.htm#Editors">other editors/IDEs</a> as well, but these are the 2 I have experience with and have enjoyed.</p>

<h2 id="run-scripts-automatically-at-startup">Run scripts automatically at startup</h2>

<p>While it’s cool that you can simply double-click your script file to run it, you likely don’t want to have to do this every time you restart your computer.</p>

<p>You can have your script run automatically when you log into Windows by doing the following:</p>

<ol>
  <li>Open File Explorer.</li>
  <li>In the address bar, type <code class="language-plaintext highlighter-rouge">shell:Startup</code> and hit enter.
  <img src="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/FileExplorerShellStartupCommand.png" alt="File Explorer shell startup command" /></li>
  <li>You should now be in your user’s Startup directory.
Every file in this directory will be executed automatically when the user logs into Windows.</li>
  <li>Find your .ahk script file and copy it.</li>
  <li>Back in your user’s Startup directory, paste a shortcut to your .ahk file*.
  <img src="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/FileExplorerStartUpDirectory.png" alt="Paste shortcut into Startup directory" /></li>
</ol>

<p>That’s it.
Now the next time the user logs into Windows, the script will be started automatically.</p>

<p>*You don’t <em>need</em> to paste a shortcut; you could paste the actual .ahk file itself in the Startup directory and things would work as expected.
However, I prefer to keep the actual .ahk file somewhere that it will be backed up, such as in a Git repository, or in a OneDrive folder.</p>

<h2 id="allow-ahk-to-interact-with-apps-running-as-admin">Allow AHK to interact with apps running as admin</h2>

<p>If you run other applications as admin on your computer, you may notice that AHK cannot interact with them.
This is because in order for AHK to interact with applications running as admin, it either also needs to run as admin, or we need to digitally sign the AutoHotkey.exe.</p>

<p>I recommend digitally signing the AutoHotkey executable.
It’s a one-time setup operation and only takes about 30 seconds to do.
To digitally sign the AutoHotkey executable:</p>

<ol>
  <li>Download and unzip <a href="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/EnableUIAccess.zip">the EnableUIAccess.zip file</a>.</li>
  <li>Double-click the <code class="language-plaintext highlighter-rouge">EnableUIAccess.ahk</code> script to run it, and it will automatically prompt you.</li>
  <li>Read the disclaimer and click <code class="language-plaintext highlighter-rouge">OK</code>.</li>
  <li>On the <code class="language-plaintext highlighter-rouge">Select Source File</code> prompt choose the “C:\Program Files\AutoHotkey\AutoHotkey.exe” file; it’s typically already selected by default so you can just hit the <code class="language-plaintext highlighter-rouge">Open</code> button. (Might be Program Files (x86) if you have 32-bit AHK installed on 64-bit Windows)</li>
  <li>On the <code class="language-plaintext highlighter-rouge">Select Destination File</code> prompt choose the same “C:\Program Files\AutoHotkey\AutoHotkey.exe” file. Again, this is typically already selected by default so you can just hit the <code class="language-plaintext highlighter-rouge">Save</code> button.</li>
  <li>Click <code class="language-plaintext highlighter-rouge">Yes</code> to replace the existing file.</li>
  <li>Click <code class="language-plaintext highlighter-rouge">Yes</code> when prompted to <code class="language-plaintext highlighter-rouge">Run With UI Access</code>.</li>
</ol>

<p>That’s it; AutoHotkey should now be able to interact with all windows/applications, even ones running as admin.
I’ve blogged about this in the past, so if you’re interested you can <a href="https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin">see this post</a> for more background information.</p>

<h2 id="transform-your-script-into-an-executable">Transform your script into an executable</h2>

<p>Perhaps you’ve written a cool AHK script that you want to share with your friends, but you don’t want them to have to install AutoHotkey.
AutoHotkey has you covered!
Simply right-click on your .ahk script file and choose <code class="language-plaintext highlighter-rouge">Compile Script</code>.</p>

<p><img src="/assets/Posts/2020-09-28-Get-up-and-running-with-AutoHotkey/AhkContextMenu.png" alt="AHK context menu" /></p>

<p>This will create a .exe executable from your script file.
Double-clicking the .exe file will have the same result as double-clicking the .ahk file to run it, except the .exe does not require that AutoHotkey be installed on the computer.</p>

<h2 id="a-few-quick-ahk-script-examples">A few quick AHK script examples</h2>

<p>To give a few examples of the types of things you can do with AutoHotkey:</p>

<ul>
  <li>
    <p>Hotkey to expand your full address when you type <code class="language-plaintext highlighter-rouge">myaddress</code>:</p>

    <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">::</span><span class="n">myaddress</span><span class="p">::</span><span class="m">123</span> <span class="n">My</span> <span class="n">Street</span><span class="p">,</span> <span class="n">My</span> <span class="n">City</span><span class="p">,</span> <span class="n">My</span> <span class="n">Province</span><span class="p">/</span><span class="n">State</span><span class="p">,</span> <span class="n">My</span> <span class="n">Postal</span><span class="p">/</span><span class="n">Zip</span> <span class="n">Code</span><span class="p">,</span> <span class="n">My</span> <span class="n">Country</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Hotkey to open a new email message for you to send when you press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>e</kbd>:</p>

    <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">^+</span><span class="n">e</span><span class="p">::</span><span class="n">Run</span><span class="p">,</span> <span class="n">mailto</span><span class="p">:</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Hotkey to open a frequent directory when you press <kbd>Windows Key</kbd> + <kbd>o</kbd>:</p>

    <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span><span class="n">o</span><span class="p">::</span><span class="n">Run</span><span class="p">,</span> <span class="n">C</span><span class="p">:</span><span class="err">\</span><span class="n">Some</span> <span class="n">folder</span> <span class="n">I</span> <span class="n">open</span> <span class="n">often</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Hotkey to open a frequent website when you press <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>w</kbd>:</p>

    <div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">^!</span><span class="n">w</span><span class="p">::</span><span class="n">Run</span><span class="p">,</span> <span class="n">http</span><span class="p">:</span><span class="c1">//Some.Website.com</span>
</code></pre></div>    </div>
  </li>
</ul>

<p><a href="https://www.lifehack.org/articles/featured/10-ways-to-use-autohotkey-to-rock-your-keyboard.html">Lifehack has a few more useful script examples</a> if you’d like to check them out.</p>

<p>You can do way more than quick one-line scripts as well.
For example, if you use Zoom <a href="https://blog.danskingdom.com/Close-those-superfluous-Zoom-windows-automatically">check out this post</a> that gives an AHK script that will close excess Zoom windows.
Hopefully these examples will peek your curiosity 😊</p>

<h2 id="conclusion">Conclusion</h2>

<p>Now that you know how easy it is to run AHK code, check out <a href="https://www.autohotkey.com/docs/Tutorial.htm">the official quick start tutorial</a> to learn the AHK language syntax and common operations and commands.
The tutorial and <a href="https://www.autohotkey.com/docs/AutoHotkey.htm">documentation</a> will help you discover how truly powerful the language is, and will hopefully give you some ideas on how you can automate more of your daily tasks.</p>

<blockquote>
  <p>Shameless Plug: You can also checkout my open source project <a href="https://github.com/deadlydog/AHKCommandPicker">AHK Command Picker</a> that allows you to use a GUI picker instead of having to remember a ton of keyboard shortcuts.</p>
</blockquote>

<p>Happy automating!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="AutoHotkey" /><category term="AHK" /><summary type="html"><![CDATA[AutoHotkey (AHK) is an amazing programming language for automating tasks and keystrokes in Windows. In this post we’ll look at how you can get your first AHK script up and running, and other configurations and processes you may want to adopt. I’ve given some of these AHK setup instructions in previous posts, but figured they deserved their own dedicate post.]]></summary></entry><entry><title type="html">Git alias to reset local tags</title><link href="https://blog.danskingdom.com/Git-alias-to-reset-local-tags/" rel="alternate" type="text/html" title="Git alias to reset local tags" /><published>2020-09-21T00:00:00+00:00</published><updated>2020-09-21T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Git-alias-to-reset-local-tags</id><content type="html" xml:base="https://blog.danskingdom.com/Git-alias-to-reset-local-tags/"><![CDATA[<p>I’ve noticed that VS Code sometimes detects conflicts between my local Git tags and remote ones.
This results in a Git error when hitting the small Git sync button in the VS Code bottom toolbar.</p>

<p><img src="/assets/Posts/2020-09-21-Git-alias-to-reset-local-tags/VsCodeSyncIcon.png" alt="VS Code sync icon" /></p>

<p>To easily solve this issue, I setup this alias in my <code class="language-plaintext highlighter-rouge">.gitconfig</code> to easily wipe my local tags and reset them to what the remote has:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span><span class="nb">alias</span><span class="o">]</span>
    delete-local-tags <span class="o">=</span> <span class="o">!</span><span class="nb">echo</span> <span class="s1">'git tag -l | xargs git tag -d &amp;&amp; git fetch --tags'</span> <span class="o">&amp;&amp;</span> git tag <span class="nt">-l</span> | xargs git tag <span class="nt">-d</span> <span class="o">&amp;&amp;</span> git fetch <span class="nt">--tags</span>
</code></pre></div></div>

<p>This snippet assumes you’re running Git in a Bash prompt.</p>

<p>Now when I encounter this error, from the command line I just type <code class="language-plaintext highlighter-rouge">git delete-local-tags</code> and it resets my local tags to what the remote has, making VS Code happy and enabling me to sync Git with a single button click again.</p>

<p>The <code class="language-plaintext highlighter-rouge">!echo '[string]'</code> portion isn’t required as it will simply display the command that is about to run, but I like to know what my aliases are doing behind the scenes when I run them.</p>

<p>Happy syncing :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Git" /><category term="Productivity" /><category term="Git" /><category term="Productivity" /><category term="Tags" /><summary type="html"><![CDATA[I’ve noticed that VS Code sometimes detects conflicts between my local Git tags and remote ones. This results in a Git error when hitting the small Git sync button in the VS Code bottom toolbar.]]></summary></entry><entry><title type="html">Watch videos faster and reclaim your time</title><link href="https://blog.danskingdom.com/Watch-videos-faster-and-reclaim-your-time/" rel="alternate" type="text/html" title="Watch videos faster and reclaim your time" /><published>2020-09-12T00:00:00+00:00</published><updated>2020-09-12T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Watch-videos-faster-and-reclaim-your-time</id><content type="html" xml:base="https://blog.danskingdom.com/Watch-videos-faster-and-reclaim-your-time/"><![CDATA[<p>Your time is valuable, and limited.
Many videos we watch, whether they’re conference sessions, DIY instructions, movies, or silly cat videos, can easily be understood and enjoyed while watching at a faster playback speed.</p>

<p>I typically find increasing the playback speed most valuable on informational and instructional videos, such as presentations and DIY videos.
It also comes in handy for shows with a lot of slow moving cutscenes (e.g. Game of Thrones), or if my wife has watched a few episodes of our show without me and I need to catch up.</p>

<h2 id="browser-extension-for-online-videos">Browser extension for online videos</h2>

<p>To make the most of my time, I use the <code class="language-plaintext highlighter-rouge">Video Speed Controller</code> browser extension:</p>

<ul>
  <li><a href="https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk">Chrome extension</a></li>
  <li><a href="https://addons.mozilla.org/en-US/firefox/addon/videospeed/">Firefox extension</a></li>
</ul>

<p>The extension allows you to speed up or slowdown any HTML5 video in small increments by adding semi-transparent controls to the top-left corner of the video when your mouse is over the video; if your mouse is not over the video, it does not overlay anything on the video.</p>

<p><img src="/assets/Posts/2020-09-12-Watch-videos-faster-and-reclaim-your-time/VideoSpeedControllerOverlay.png" alt="Video Speed Controller speed overlay" />
<img src="/assets/Posts/2020-09-12-Watch-videos-faster-and-reclaim-your-time/VideoSpeedControllerOverlayControls.png" alt="Video Speed Controller controls overlay" /></p>

<p>It also provides <a href="https://github.com/igrigorik/videospeed#install-chrome-extension">customizable keyboard shortcuts</a> that can be used to control the playback speed and jump forward and back in the video, which is very handy.</p>

<p>See the <a href="https://github.com/igrigorik/videospeed">GitHub repository</a> for more information and features.</p>

<p>I was not able to find the Video Speed Controller extension for Edge, but hopefully it will be developed soon, or you can try looking for other similar extensions for your other browsers.</p>

<h3 id="why-do-i-need-this">Why do I need this?</h3>

<p>For a few reasons:</p>

<ol>
  <li><strong>Works everywhere</strong>*: Many sites, such as YouTube, already allow you to adjust the playback speed.
However, many sites do not.
This extension allows you to control the playback speed on videos, even when the video hosting platform does not.
    <ul>
      <li>*This extension only works with HTML5 video, which is what most sites use these days. If a site uses something else for their videos, like Flash, the extension may not work.</li>
      <li>**I have found that the mouse controls are not accessible when viewing videos on FaceBook, however the keyboard shortcuts still work.</li>
    </ul>
  </li>
  <li><strong>Fine-grained control</strong>: Even when sites give you playback speed control, they often only allow you to speed up the video by increments of 0.5x, so you have to jump straight from 1.0x speed to 1.5x, to 2.0x.
I find my sweet spot is typically around 1.7x, but it depends on the speaker and content.
In addition to playback speed, you can also easily jump forward and back 10 seconds in the video, which is much more precise than using the mouse and the video slider.</li>
  <li><strong>Consistency</strong>: The playback speed controls are easily accessible and always the same.
You don’t have to dig through menus to find them, and they are always in the same spot no matter what site you are watching a video on.</li>
</ol>

<h2 id="phones-tablets-and-media-streaming-devices">Phones, tablets, and media streaming devices</h2>

<p>I haven’t found an app or browser that does this same thing for phones, tablets, or media streaming devices (such as Roku, Chromecast, Fire TV), so if you know of one please let me know in the comments!</p>

<h2 id="offline-videos">Offline videos</h2>

<p>If you’re watching videos that you have stored on your local machine or a network share, I recommend using <code class="language-plaintext highlighter-rouge">VLC</code>.</p>

<ul>
  <li><a href="https://www.videolan.org/vlc/index.html">Download VLC</a></li>
</ul>

<p>You can control the playback speed via the Playback &gt; Speed menu; this menu is also accessible via a right-click context menu.</p>

<p><img src="/assets/Posts/2020-09-12-Watch-videos-faster-and-reclaim-your-time/VlcPlaybackSpeedControls.png" alt="VLC playback speed controls" /></p>

<p>It also provides configurable hotkeys that can be used.
The default hotkeys are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">+</code> - Increase playback speed by 0.5x.</li>
  <li><code class="language-plaintext highlighter-rouge">-</code> - Decrease playback speed by 0.5x.</li>
  <li><kbd>]</kbd> - Increase playback speed by 0.1x.</li>
  <li><kbd>[</kbd> - Decrease playback speed by 0.1x.</li>
  <li><code class="language-plaintext highlighter-rouge">=</code> - Reset playback speed to 1.0.</li>
  <li><kbd>Spacebar</kbd> - Pause playback.</li>
  <li><code class="language-plaintext highlighter-rouge">Right arrow</code> - Jump ahead 10 seconds.</li>
  <li><code class="language-plaintext highlighter-rouge">Left arrow</code> - Jump back 10 seconds.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Whether watching videos online or offline, make the most of your time by controlling the playback speed, and easily jumping ahead or back in the video.
How much you increase the speed will depend on how fast the people in the video speak, as well as the content of the video.
Being able to adjust the playback speed by small amounts allows you to find the sweet spot for that particular video.</p>

<p>As more people transition to working from home, work presentations are often recorded and available to watch later.
If you know a presentation will be recorded, skip watching it live and instead burn through that 60 minute video in 40 minutes later.
Give some extra minutes back to your life every day by watching videos faster.</p>

<p>Happy watching!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="Productivity" /><category term="Video" /><summary type="html"><![CDATA[Your time is valuable, and limited. Many videos we watch, whether they’re conference sessions, DIY instructions, movies, or silly cat videos, can easily be understood and enjoyed while watching at a faster playback speed.]]></summary></entry><entry><title type="html">Bring up the Windows Terminal in a keystroke</title><link href="https://blog.danskingdom.com/Bring-up-the-Windows-Terminal-in-a-keystroke/" rel="alternate" type="text/html" title="Bring up the Windows Terminal in a keystroke" /><published>2020-08-22T00:00:00+00:00</published><updated>2021-07-07T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Bring-up-the-Windows-Terminal-in-a-keystroke</id><content type="html" xml:base="https://blog.danskingdom.com/Bring-up-the-Windows-Terminal-in-a-keystroke/"><![CDATA[<p>I decided to try out the new <a href="https://github.com/microsoft/terminal">Windows Terminal</a> to see how it compared to <a href="https://conemu.github.io">ConEmu</a>, which is my usual console.
The recommended way to get the Windows Terminal is to <a href="https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701">download it from the Microsoft Store</a> so that it can automatically update itself as new versions are released.</p>

<p>While the Windows Terminal is not as mature and feature rich as ConEmu, I did enjoy it, and it’s being actively worked on with plenty of features coming down the road.
I’d also recommend following <a href="https://www.hanselman.com/blog/HowToMakeAPrettyPromptInWindowsTerminalWithPowerlineNerdFontsCascadiaCodeWSLAndOhmyposh.aspx">Scott Hanselman’s post about how to make it look nicer</a>.</p>

<p>One feature I missed right away was ConEmu allows you to set a keyboard shortcut to put it in focus.
As a software developer, I’m constantly in and out of the terminal, and being able to get to it in a keystroke is very convenient.</p>

<h2 id="method-1-pin-windows-terminal-to-the-taskbar">Method 1: Pin Windows Terminal to the taskbar</h2>

<p>The easiest way to get to the Windows Terminal using a keyboard shortcut is to pin it to the taskbar.
Not only does it make it easy to click on with the mouse, but you can also use the <kbd>Windows Key</kbd> + <kbd>[number]</kbd> keyboard shortcut to launch it or put it in focus.</p>

<p>For example, if on your taskbar from left to right you have: Edge, Chrome, Windows Terminal, then you could use <kbd>Windows Key</kbd> + <kbd>3</kbd> to launch the Windows Terminal, or put it in focus if it’s already open.
Similarly, <kbd>Windows Key</kbd> + <kbd>1</kbd> would launch Edge, and <kbd>Windows Key</kbd> + <kbd>2</kbd> would launch Chrome.</p>

<p>This is a simple solution and it works, but the reason I’m not a fan of it is:</p>

<ol>
  <li>If I reorder the windows on the taskbar then the keyboard shortcut changes.</li>
  <li>This method only works for the first 10 items on the taskbar. i.e. you can’t do <kbd>Windows Key</kbd> + <kbd>11</kbd>.</li>
  <li>I find it awkward to use the Windows Key with any numbers greater than 4.</li>
</ol>

<p>So let’s continue exploring other options.</p>

<h2 id="method-2-launch-windows-terminal-from-the-command-line">Method 2: Launch Windows Terminal from the command line</h2>

<p>The Windows Terminal is installed as a Microsoft Store app, so the location of the executable isn’t very obvious, and it is likely to change every time the app is updated.
This can make launching it via other applications or scripts tough.
Luckily, I found <a href="https://answers.microsoft.com/en-us/windows/forum/windows_10-windows_store/starting-windows-10-store-app-from-the-command/836354c5-b5af-4d6c-b414-80e40ed14675">this wonderful post describing how to launch Microsoft Store apps from the command line</a>.</p>

<p>From there, I was able to track down that you can launch the Windows Terminal store app using:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>explorer.exe shell:AppsFolder\Microsoft.WindowsTerminal_8wekyb3d8bbwe!App
</code></pre></div></div>

<blockquote>
  <p>Update: It turns out you can also simply run <code class="language-plaintext highlighter-rouge">wt</code> from the command line to launch the Windows Terminal, as <code class="language-plaintext highlighter-rouge">wt.exe</code> gets added to the Windows PATH when the Windows Terminal is installed.</p>
</blockquote>

<p>Now that we know how to launch it from the command line, you can use this from any custom scripts or application launchers you might use.
This is in fact what I show how to do from AutoHotkey further below.</p>

<p>While this command allows us to launch Windows Terminal, it doesn’t allow us to put it in focus if it’s already open, so let’s continue.</p>

<h2 id="method-3-launch-windows-terminal-via-a-custom-keyboard-shortcut">Method 3: Launch Windows Terminal via a custom keyboard shortcut</h2>

<p>The above post mentions that you can simply navigate to <code class="language-plaintext highlighter-rouge">shell:AppsFolder</code>, find the Windows Terminal app, right-click on it, and choose <code class="language-plaintext highlighter-rouge">Create shortcut</code>.
This will put a shortcut to the application on your desktop, and like any shortcut file in Windows, you can right-click on it, go to <code class="language-plaintext highlighter-rouge">Properties</code>, and assign it a <code class="language-plaintext highlighter-rouge">Shortcut key</code> that can be used to launch the application.</p>

<p>While this will allow you to launch the Windows Terminal with a custom keyboard shortcut, like the previous method, it opens a new instance of the Windows Terminal every time, which isn’t what I want, so let’s continue.</p>

<h2 id="method-4-switch-to-windows-terminal-via-keyboard-shortcut-and-autohotkey">Method 4: Switch to Windows Terminal via keyboard shortcut and AutoHotkey</h2>

<p>To use a custom keyboard shortcut to both launch the Windows Terminal, as well as simply switch to the Windows Terminal window if it’s already open, I use <a href="https://www.autohotkey.com">AutoHotkey</a>.
I’ve <a href="https://blog.danskingdom.com/categories/#autohotkey">blogged about AutoHotkey in the past</a>, and if you’ve never used it you really should check it out.</p>

<p>In a new or existing AutoHotkey script, you can define this function and hotkey:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">SwitchToWindowsTerminal</span><span class="p">()</span>
<span class="p">{</span>
  <span class="n">windowHandleId</span> <span class="p">:=</span> <span class="nf">WinExist</span><span class="p">(</span><span class="s">"ahk_exe WindowsTerminal.exe"</span><span class="p">)</span>
  <span class="n">windowExistsAlready</span> <span class="p">:=</span> <span class="n">windowHandleId</span> <span class="p">&gt;</span> <span class="m">0</span>

  <span class="p">;</span> <span class="n">If</span> <span class="n">the</span> <span class="n">Windows</span> <span class="n">Terminal</span> <span class="k">is</span> <span class="n">already</span> <span class="n">open</span><span class="p">,</span> <span class="n">determine</span> <span class="k">if</span> <span class="n">we</span> <span class="n">should</span> <span class="n">put</span> <span class="n">it</span> <span class="k">in</span> <span class="n">focus</span> <span class="n">or</span> <span class="n">minimize</span> <span class="n">it</span><span class="p">.</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">windowExistsAlready</span> <span class="p">=</span> <span class="k">true</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">activeWindowHandleId</span> <span class="p">:=</span> <span class="nf">WinExist</span><span class="p">(</span><span class="s">"A"</span><span class="p">)</span>
    <span class="n">windowIsAlreadyActive</span> <span class="p">:=</span> <span class="n">activeWindowHandleId</span> <span class="p">==</span> <span class="n">windowHandleId</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">windowIsAlreadyActive</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="p">;</span> <span class="n">Minimize</span> <span class="n">the</span> <span class="n">window</span><span class="p">.</span>
      <span class="n">WinMinimize</span><span class="p">,</span> <span class="s">"ahk_id %windowHandleId%"</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
      <span class="p">;</span> <span class="n">Put</span> <span class="n">the</span> <span class="n">window</span> <span class="k">in</span> <span class="n">focus</span><span class="p">.</span>
      <span class="n">WinActivate</span><span class="p">,</span> <span class="s">"ahk_id %windowHandleId%"</span>
      <span class="n">WinShow</span><span class="p">,</span> <span class="s">"ahk_id %windowHandleId%"</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="p">;</span> <span class="n">Else</span> <span class="n">it</span><span class="err">'</span><span class="n">s</span> <span class="n">not</span> <span class="n">already</span> <span class="n">open</span><span class="p">,</span> <span class="n">so</span> <span class="n">launch</span> <span class="n">it</span><span class="p">.</span>
  <span class="k">else</span>
  <span class="p">{</span>
    <span class="n">Run</span><span class="p">,</span> <span class="n">wt</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="p">;</span> <span class="n">Hotkey</span> <span class="n">to</span> <span class="n">use</span> <span class="n">Ctrl</span><span class="p">+</span><span class="n">Shift</span><span class="p">+</span><span class="n">C</span> <span class="n">to</span> <span class="n">launch</span><span class="p">/</span><span class="n">restore</span> <span class="n">the</span> <span class="n">Windows</span> <span class="n">Terminal</span><span class="p">.</span>
<span class="p">^+</span><span class="n">c</span><span class="p">::</span><span class="nf">SwitchToWindowsTerminal</span><span class="p">()</span>
</code></pre></div></div>

<p>The last line in the script defines the keyboard shortcut and has it call the function.</p>

<p>Here I’m using <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd> (^+c) for my keyboard shortcut, but you could use something else like <kbd>Windows Key</kbd> + <kbd>C</kbd> (#c) or <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>C</kbd> (^!c).
Check out the <a href="https://www.autohotkey.com/docs/KeyList.htm">AutoHotkey key list</a> for other non-obvious key symbols.</p>

<p>You may have also noticed in the code that if the window is already in focus, we minimize it.
This allows me to easily switch to and away from the Windows Terminal using the same shortcut keys.
Using <kbd>Alt</kbd> + <kbd>Tab</kbd> would also work to switch back to your previous application.</p>

<p>Finally, the line that actually launches the Windows Terminal is:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Run</span><span class="p">,</span> <span class="n">wt</span>
</code></pre></div></div>

<p>If you want the Windows Terminal launched as an elevated command prompt (i.e. as Admin), then you need to add <code class="language-plaintext highlighter-rouge">*RunAs</code>, like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Run</span><span class="p">,</span> <span class="p">*</span><span class="n">RunAs</span> <span class="n">wt</span>
</code></pre></div></div>

<p>Also, if you haven’t already, you’ll want to <a href="https://blog.danskingdom.com/2020-09-28-Get-up-and-running-with-AutoHotkey.md#allow-ahk-to-interact-with-apps-running-as-admin">follow these steps to digitally sign your AutoHotkey executable</a> to allow AutoHotkey scripts to be able to interact with applications running as Admin.
Without this, the AutoHotkey script will not be able to minimize the Windows Terminal.</p>

<h3 id="getting-the-autohotkey-script-running">Getting the AutoHotkey script running</h3>

<p>If you’ve never used AutoHotkey before and want to get this working, all you need to do is:</p>

<ol>
  <li>Install <a href="https://www.autohotkey.com">AutoHotkey</a></li>
  <li>Create a new text file with a <code class="language-plaintext highlighter-rouge">.ahk</code> extension</li>
  <li>Copy-paste the above script into the file</li>
  <li>Double click the file to run it.</li>
</ol>

<p>Once that’s done, you should be able to use your keyboard shortcut to switch to Windows Terminal.</p>

<p>You’ll also likely want to have your script startup automatically with Windows so that you don’t have to manually run it all the time.
This is as easy as dropping the .ahk file (or a shortcut to it) in your <code class="language-plaintext highlighter-rouge">shell:Startup</code> directory.
Windows will automatically run all files in this directory every time you log in.</p>

<p>For more detailed instructions and information, <a href="https://blog.danskingdom.com/Get-up-and-running-with-AutoHotkey/">see this post</a></p>

<h2 id="conclusion">Conclusion</h2>

<p>As a software developer, I’m constantly in and out of the terminal for running Git and PowerShell commands.
If you’re ok simply pinning Windows Terminal to the taskbar in a dedicated position and don’t mind the preset keyboard shortcut, then roll with that.
For myself, using AutoHotkey I can now use <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd> to switch to and away from the Windows Terminal at anytime, no matter what other application currently has focus, and not having to reach for the mouse.</p>

<blockquote>
  <p>Shameless Plug: You can also checkout my open source project <a href="https://github.com/deadlydog/AHKCommandPicker">AHK Command Picker</a> that allows you to use a GUI picker instead of having to remember a ton of keyboard shortcuts.</p>
</blockquote>

<p>I hope you’ve found this information helpful.</p>

<p>Happy command lining :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows Terminal" /><category term="AutoHotkey" /><category term="Microsoft Store" /><category term="Productivity" /><category term="Shortcuts" /><category term="Windows Terminal" /><category term="AutoHotkey" /><category term="Microsoft Store" /><category term="AHK" /><category term="Keyboard shortcuts" /><summary type="html"><![CDATA[I decided to try out the new Windows Terminal to see how it compared to ConEmu, which is my usual console. The recommended way to get the Windows Terminal is to download it from the Microsoft Store so that it can automatically update itself as new versions are released.]]></summary></entry><entry><title type="html">Fixing SQL72023 Containment error when deploying SQL SSDT database project from a dacpac</title><link href="https://blog.danskingdom.com/Fixing-SQL72023-Containment-error-when-deploying-SQL-SSDT-database-project-from-a-dacpac/" rel="alternate" type="text/html" title="Fixing SQL72023 Containment error when deploying SQL SSDT database project from a dacpac" /><published>2020-06-15T00:00:00+00:00</published><updated>2020-06-16T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Fixing-SQL72023-Containment-error-when-deploying-SQL-SSDT-database-project-from-a-dacpac</id><content type="html" xml:base="https://blog.danskingdom.com/Fixing-SQL72023-Containment-error-when-deploying-SQL-SSDT-database-project-from-a-dacpac/"><![CDATA[<p>We setup a new Visual Studio Database Project using SSDT (SQL Server Data Tools) and built it, which generates a .dapac file that can be used for deployments. While performing an automated deploy using the .dapac file, we encountered the following error:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*** Could not deploy package.
Warning SQL72023: The database containment option has been changed to None.  This may result in deployment failure if the state of the database is not compliant with this containment level.
Error SQL72014: .Net SqlClient Data Provider: Msg 5061, Level 16, State 1, Line 5 ALTER DATABASE failed because a lock could not be placed on database 'Example_SqlDatabase'. Try again later.
Error SQL72045: Script execution error.  The executed script:
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
Time elapsed 00:00:32.67
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET CONTAINMENT = NONE
            WITH ROLLBACK IMMEDIATE;
    END

Error SQL72014: .Net SqlClient Data Provider: Msg 5069, Level 16, State 1, Line 5 ALTER DATABASE statement failed.
Error SQL72045: Script execution error.  The executed script:
IF EXISTS (SELECT 1
           FROM   [master].[dbo].[sysdatabases]
           WHERE  [name] = N'$(DatabaseName)')
    BEGIN
        ALTER DATABASE [$(DatabaseName)]
            SET CONTAINMENT = NONE
            WITH ROLLBACK IMMEDIATE;
    END
</code></pre></div></div>

<p>On the same morning, a different team ran into this same issue with one of their databases, but with a slightly different error message:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*** Could not deploy package.
Warning SQL72023: The database containment option has been changed to None.  This may result in deployment failure if the state of the database is not compliant with this containment level.
Error SQL72014: .Net SqlClient Data Provider: Msg 12809, Level 16, State 1, Line 5 You must remove all users with password before setting the containment property to NONE.
</code></pre></div></div>

<p>The root cause of both is the same; It seems the deployment was trying to set the database <code class="language-plaintext highlighter-rouge">Containment</code> mode to <code class="language-plaintext highlighter-rouge">None</code>, which requires additional permissions and isn’t allowed when using SQL Availability Groups.</p>

<p>The fix for this is to modify the .sqlproj file to change it’s default <code class="language-plaintext highlighter-rouge">Containment</code> mode.
You can do this from Visual Studio by right-clicking on the project in Solution Explorer, choosing <code class="language-plaintext highlighter-rouge">Properties</code>, in the <code class="language-plaintext highlighter-rouge">Project Settings</code> tab click the <code class="language-plaintext highlighter-rouge">Database Settings...</code> button, then in the <code class="language-plaintext highlighter-rouge">Miscellaneous</code> tab change the value of the <code class="language-plaintext highlighter-rouge">Containment</code> drop-down to <code class="language-plaintext highlighter-rouge">None</code> or <code class="language-plaintext highlighter-rouge">Partial</code> as needed.</p>

<p><img src="/assets/Posts/2020-06-15-Fixing-SQL72023-Containment-error-when-deploying-SQL-SSDT-database-project-from-a-dacpac/SetVisualStudioDatabaseProjectContainmentMode.png" alt="How to change the Containment mode from Visual Studio" /></p>

<p>Changing that value from <code class="language-plaintext highlighter-rouge">None</code> to <code class="language-plaintext highlighter-rouge">Partial</code> ends up adding the following element to the database’s .sqlproj file:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Containment&gt;</span>Partial<span class="nt">&lt;/Containment&gt;</span>
</code></pre></div></div>

<p>One of the teams reported that they also had to go into their database project settings and change the <code class="language-plaintext highlighter-rouge">Target platform</code> from <code class="language-plaintext highlighter-rouge">SQL Server 2008</code> to <code class="language-plaintext highlighter-rouge">SQL Server 2016</code> before the above change would work.</p>

<p>If you encounter this problem as well, hopefully this helps get you going.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="SQL" /><category term="Database" /><category term="Deploy" /><category term="SQL" /><category term="Database" /><category term="Deploy" /><category term="SSDT" /><summary type="html"><![CDATA[We setup a new Visual Studio Database Project using SSDT (SQL Server Data Tools) and built it, which generates a .dapac file that can be used for deployments. While performing an automated deploy using the .dapac file, we encountered the following error:]]></summary></entry><entry><title type="html">Custom version numbers in Azure DevOps yaml pipelines</title><link href="https://blog.danskingdom.com/Custom-version-numbers-in-Azure-DevOps-yaml-pipelines/" rel="alternate" type="text/html" title="Custom version numbers in Azure DevOps yaml pipelines" /><published>2020-01-10T00:00:00+00:00</published><updated>2022-03-13T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Custom-version-numbers-in-Azure-DevOps-yaml-pipelines</id><content type="html" xml:base="https://blog.danskingdom.com/Custom-version-numbers-in-Azure-DevOps-yaml-pipelines/"><![CDATA[<p>I’m a fan of <a href="https://semver.org/">semantic versioning</a>, especially for software meant to be consumed by other developers, such as NuGet packages.
If you want to use semantic versioning however, it means you need to have some control over the version number that gets assigned to your software by your build system.
Whether you are looking to use semantic versioning, or want to use some other version number format, in this post we will look at how to accomplish that when using yaml files for you Azure Pipeline.</p>

<h2 id="using-the-classic-editor">Using the classic editor</h2>

<p>Before we look at the yaml way, if you’ve been using Azure DevOps for a while you may have already solved this problem in the classic build pipeline editor.
One way to do this was to use the <code class="language-plaintext highlighter-rouge">$(Rev:r)</code> syntax in your <code class="language-plaintext highlighter-rouge">Build number format</code>; for example, using <code class="language-plaintext highlighter-rouge">1.0.0.$(Rev:r)</code>.</p>

<p><img src="/assets/Posts/2020-01-10-Custom-version-numbers-in-Azure-DevOps-yaml-pipelines/AzurePipelinesClassicEditorBuildNumberFormat.png" alt="Azure Pipelines classic editor build number format" /></p>

<p>The <code class="language-plaintext highlighter-rouge">$(Rev:r)</code> syntax acts as a variable with an auto-incrementing value, so the first build would be <code class="language-plaintext highlighter-rouge">1.0.0.0</code>, the next would be <code class="language-plaintext highlighter-rouge">1.0.0.1</code>, then <code class="language-plaintext highlighter-rouge">1.0.0.2</code>, and so on.
Once any part of the string to the left of <code class="language-plaintext highlighter-rouge">$(Rev:r)</code> changes, the counter resets to zero.
So if you changed the Build Number Format to <code class="language-plaintext highlighter-rouge">1.1.0.$(Rev:r)</code>, the next build would have a value of <code class="language-plaintext highlighter-rouge">1.1.0.0</code>.</p>

<p>To access the Build Number Format value in your tasks so that you could actually use it, you would use the built-in <code class="language-plaintext highlighter-rouge">$(Build.BuildNumber)</code> variable.
For example, if you wanted to apply the version to all of your .Net projects before building the assemblies, you could do this:</p>

<p><img src="/assets/Posts/2020-01-10-Custom-version-numbers-in-Azure-DevOps-yaml-pipelines/AzurePipelinesClassicEditorUsingBuildNumberFormat.png" alt="Azure Pipelines classic editor using build number format" /></p>

<p>I am a huge fan of <a href="https://marketplace.visualstudio.com/items?itemName=richardfennellBM.BM-VSTS-Versioning-Task">Richard Fennell’s Manifest Versioning Build Tasks Azure DevOps extension</a>, which is what is being used in the above screenshot to version all of the .Net assemblies with our version number.</p>

<blockquote>
  <p>NOTE: You’ll need to have the extension installed in order to use the <code class="language-plaintext highlighter-rouge">richardfennellBM.BM-VSTS-Versioning-Task.Version-Assemblies-Task.VersionAssemblies@2</code> task shown in yaml snippets below.
You may be able to simply use <code class="language-plaintext highlighter-rouge">VersionAssemblies@2</code>, but it conflicts with other extensions I have installed so I use the fully qualified name here to avoid the ambiguity error.</p>
</blockquote>

<h2 id="simple-yaml-solution">Simple yaml solution</h2>

<p>Microsoft is moving away from the classic editor and investing in yaml pipelines.
To accomplish the same thing as described in the above classic editor scenario is very easy to do in yaml, and the code would look like this:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.0.0.$(Rev:r)'</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">richardfennellBM.BM-VSTS-Versioning-Task.Version-Assemblies-Task.VersionAssemblies@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Version the assemblies</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">Path</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(Build.SourcesDirectory)'</span>
    <span class="na">VersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(Build.BuildNumber)'</span>
    <span class="na">InjectVersion</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">FilenamePattern</span><span class="pi">:</span> <span class="s1">'</span><span class="s">AssemblyInfo.*'</span>
    <span class="na">OutputVersion</span><span class="pi">:</span> <span class="s1">'</span><span class="s">OutputedVersion'</span>
</code></pre></div></div>

<p>In the yaml definition, <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/run-number?view=azure-devops&amp;tabs=yaml">the <code class="language-plaintext highlighter-rouge">name</code> element corresponds to the <code class="language-plaintext highlighter-rouge">Build number format</code> of the classic editor</a>, but in both yaml and the classic editor the <code class="language-plaintext highlighter-rouge">$(Build.BuildNumber)</code> variable is used to access the value.</p>

<h2 id="a-bit-more-advanced-yaml">A bit more advanced yaml</h2>

<p>Having seen the simple yaml solution, there’s a few things we should mention:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">$(Rev:r)</code> auto-incrementing syntax is only valid for the <code class="language-plaintext highlighter-rouge">name</code> element; you cannot use it in any other variables or fields.</li>
  <li>The <code class="language-plaintext highlighter-rouge">name</code> (i.e. <code class="language-plaintext highlighter-rouge">Build number format</code>) is what shows up on your Azure Pipeline’s build summary page.
If you want to show more information in the build’s title, such as the git branch the build was made from or the date it was created, then this solution won’t work; something like <code class="language-plaintext highlighter-rouge">1.0.0.1_master_2020-01-15</code> is not a valid version number that can be assigned to assemblies.</li>
</ul>

<p>To overcome this problem we can make use of yaml variables and <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#counter">the <code class="language-plaintext highlighter-rouge">counter</code> function</a>.
This function provides the same functionality as the <code class="language-plaintext highlighter-rouge">$(Rev:r)</code> syntax, where you give it a prefix and if that prefix changes, the auto-incrementing integer will reset.
In addition, this function let’s us set the seed value of the auto-incrementing integer, so we can have it start from something other than zero if we want.</p>

<p>So now we can generate our version number and version our assemblies using the yaml below.
Note that I’ve switched from a 4 part version number to a 3 part one to show off how you might do semantic versioning.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(BuildDefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)_$(Rev:.r)'</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.2'</span> <span class="c1"># Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch)'</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">richardfennellBM.BM-VSTS-Versioning-Task.Version-Assemblies-Task.VersionAssemblies@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Version the assemblies</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">Path</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(Build.SourcesDirectory)'</span>
    <span class="na">VersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(versionNumber)'</span>
    <span class="na">InjectVersion</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">FilenamePattern</span><span class="pi">:</span> <span class="s1">'</span><span class="s">AssemblyInfo.*'</span>
    <span class="na">OutputVersion</span><span class="pi">:</span> <span class="s1">'</span><span class="s">OutputedVersion'</span>
</code></pre></div></div>

<p>You can see now that we’ve introduced some variables:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">version.MajorMinor</code> is the one that you would manually adjust.</li>
  <li><code class="language-plaintext highlighter-rouge">version.Patch</code> will auto-increment with each build, and reset back to zero when <code class="language-plaintext highlighter-rouge">version.MajorMinor</code> is changed.</li>
  <li><code class="language-plaintext highlighter-rouge">versionNumber</code> is the full 3-part semantic version, and is used in the assembly versioning task.</li>
</ul>

<p>You may have noticed that the <code class="language-plaintext highlighter-rouge">name</code> was changed quite a bit as well.
It now shows the name of the build definition, the git branch that the build used, the date the build was made, and a patch number.
The patch number is still appended to ensure that multiple builds made from the same branch on the same day have different names.
There are <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/run-number?view=azure-devops&amp;tabs=yaml#tokens">some other tokens</a> that <code class="language-plaintext highlighter-rouge">name</code> supports as well.</p>

<h2 id="showing-the-version-number-in-the-build-name">Showing the version number in the build name</h2>

<p>One major issue with the yaml solution above, in my opinion, is that the name of the build no longer includes the version number in it.</p>

<p>Unfortunately, getting the version number into the <code class="language-plaintext highlighter-rouge">name</code> isn’t as simple as just doing:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(BuildDefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)_$(versionNumber)'</span>
</code></pre></div></div>

<p>This is because our <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&amp;tabs=yaml%2Cbatch#understand-variable-syntax">custom yaml variables are processed at runtime</a>, and the <code class="language-plaintext highlighter-rouge">name</code> is evaluated before then.</p>

<p>To work around this issue, we can use <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&amp;tabs=bash#updatebuildnumber-override-the-automatically-generated-build-number">the UpdateBuildNumber command</a>, as in the following yaml:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.2'</span> <span class="c1"># Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch)'</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the name of the build (i.e. the Build.BuildNumber)</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $buildName = "$(versionNumber)_$(Build.SourceBranchName)"</span>
      <span class="s">Write-Host "Setting the name of the build to '$buildName'."</span>
      <span class="s">Write-Host "##vso[build.updatebuildnumber]$buildName"</span>

<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">richardfennellBM.BM-VSTS-Versioning-Task.Version-Assemblies-Task.VersionAssemblies@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Version the assemblies</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">Path</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(Build.SourcesDirectory)'</span>
    <span class="na">VersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(versionNumber)'</span>
    <span class="na">InjectVersion</span><span class="pi">:</span> <span class="no">true</span>
    <span class="na">FilenamePattern</span><span class="pi">:</span> <span class="s1">'</span><span class="s">AssemblyInfo.*'</span>
    <span class="na">OutputVersion</span><span class="pi">:</span> <span class="s1">'</span><span class="s">OutputedVersion'</span>
</code></pre></div></div>

<p>There are a couple things to notice in this yaml.
First, I changed the <code class="language-plaintext highlighter-rouge">name</code> to indicate that it will be dynamically updated.
Second, I added another task to the steps for setting the name of the build.
I’m a fan of PowerShell so I used a PowerShell task, but you could use Bash too (the syntax would be different though).</p>

<p>In the 3 lines of PowerShell you can see that I create a string of what I want the build name to be.
Here I opted to just include the version number and the git branch the build used, but you could use any of <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&amp;tabs=yaml">the other predefined variables</a> as well.
Notice though that the predefined variables used here (i.e. <code class="language-plaintext highlighter-rouge">$(Build.SourceBranchName)</code>) is different than those used directly in the <code class="language-plaintext highlighter-rouge">name</code> element (i.e. <code class="language-plaintext highlighter-rouge">$(SourceBranchName)</code>), as the <code class="language-plaintext highlighter-rouge">name</code> only supports special tokens evaluated before runtime.</p>

<p>When the build is first queued, it’s name will show up as <code class="language-plaintext highlighter-rouge">Set dynamically below in a task</code> until the PowerShell step to update it is executed.
Because of this, you may choose to have it show something else, like in the other examples above.
If you do this, I would add a comment to the <code class="language-plaintext highlighter-rouge">name</code> saying that it gets updated in a task below.</p>

<h2 id="creating-prerelease-version-numbers">Creating prerelease version numbers</h2>

<p>At the start of this post I mentioned that I’m a fan of semantic versioning.
Part of semantic versioning is supporting prerelease versions.
While not everything supports prerelease versions, such as .Net assemblies, many things do, such as NuGet package versions.</p>

<p>Defining your prerelease version can be as simple as defining a new variable, like so:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.2'</span> <span class="c1"># Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch)'</span>
  <span class="na">prereleaseVersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(versionNumber)-$(Build.SourceVersion)'</span>
</code></pre></div></div>

<p>Where <code class="language-plaintext highlighter-rouge">$(Build.SourceVersion)</code> is the git commit SHA being built.</p>

<p>I typically like to include the date and time in my prerelease version number.
Unfortunately, there isn’t a predefined variable that can be used to access the current date and time, so it takes a bit of extra effort.</p>

<p>Here is some yaml code that I typically use for my prerelease versions:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.2'</span> <span class="c1"># Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch)'</span>
  <span class="na">prereleaseVersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the prereleaseVersionNumber variable value</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $dateTime = (Get-Date -Format 'yyyyMMddTHHmmss')</span>
      <span class="s">[string] $prereleaseVersionNumber = "$(versionNumber)-ci$dateTime"</span>
      <span class="s">Write-Host "Setting the prerelease version number variable to '$prereleaseVersionNumber'."</span>
      <span class="s">Write-Host "##vso[task.setvariable variable=prereleaseVersionNumber]$prereleaseVersionNumber"</span>

<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">VersionPowerShellModule@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Update PowerShell Module Manifests version for Prerelease version</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">Path</span><span class="pi">:</span> <span class="s1">'</span><span class="s">powerShell/Module/Directory/Path'</span>
    <span class="na">VersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(prereleaseVersionNumber)'</span>
    <span class="na">InjectVersion</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>

<p>Here I’ve introduced a new <code class="language-plaintext highlighter-rouge">prereleaseVersionNumber</code> variable, as well as a PowerShell task step to set it.
The first line of the PowerShell gets the date and time in a format acceptable for prerelease semantic versions.
The second line then builds the complete prerelease version number, appending <code class="language-plaintext highlighter-rouge">-ci$dateTime</code> to the regular version number.
I use <code class="language-plaintext highlighter-rouge">ci</code> to indicate that it’s from a continuous integration build, but you don’t need to.
The fourth line then assigns the value back to the <code class="language-plaintext highlighter-rouge">prereleaseVersionNumber</code> yaml variable so it can be used in later tasks.</p>

<p>In this example I’m using the prereleaseVersionNumber to version a PowerShell module, as it supports prerelease version numbers.</p>

<h2 id="extras">Extras</h2>

<p>If you need a unique ID in your version number, you can use the <code class="language-plaintext highlighter-rouge">$(Build.BuildId)</code> <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&amp;tabs=yaml">predefined variable</a>.
This is an auto-incrementing integer that Azure DevOps increments after any build in your Azure DevOps organization; not just in your specific build pipeline.
No two builds created in your Azure DevOps should ever have the same Build ID.</p>

<h2 id="ready-to-use-code">Ready to use code</h2>

<p>Above I’ve shown you a few different variations of ways to do version numbers in yaml templates.
Hopefully I explained it well enough that you understand how to customize it for your specific needs.
That said, here’s a few code snippets that are ready for direct copy-pasting into your yaml files, where you can then use the variables in any pipeline tasks.</p>

<p>Specifying a 3-part version number with an auto-incrementing patch:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.0'</span> <span class="c1"># Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch)'</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the name of the build (i.e. the Build.BuildNumber)</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $buildName = "$(versionNumber)_$(Build.SourceBranchName)"</span>
      <span class="s">Write-Host "Setting the name of the build to '$buildName'."</span>
      <span class="s">Write-Host "##vso[build.updatebuildnumber]$buildName"</span>
</code></pre></div></div>

<p>Specifying a 4-part version number with an auto-incrementing patch:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.0'</span> <span class="c1"># Manually adjust the version number as needed. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch).$(Build.BuildId)'</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the name of the build (i.e. the Build.BuildNumber)</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $buildName = "$(versionNumber)_$(Build.SourceBranchName)"</span>
      <span class="s">Write-Host "Setting the name of the build to '$buildName'."</span>
      <span class="s">Write-Host "##vso[build.updatebuildnumber]$buildName"</span>
</code></pre></div></div>

<p>Specifying a 3-part version number with an auto-incrementing patch, along with a prerelease version number that includes the date and time of the build and the Git commit SHA:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.0'</span> <span class="c1"># Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch)'</span>
  <span class="na">prereleaseVersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the name of the build (i.e. the Build.BuildNumber)</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $buildName = "$(versionNumber)_$(Build.SourceBranchName)"</span>
      <span class="s">Write-Host "Setting the name of the build to '$buildName'."</span>
      <span class="s">Write-Host "##vso[build.updatebuildnumber]$buildName"</span>

<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the prereleaseVersionNumber variable value</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $dateTime = (Get-Date -Format 'yyyyMMddTHHmmss')</span>
      <span class="s">[string] $prereleaseVersionNumber = "$(versionNumber)-ci$dateTime+$(Build.SourceVersion)"</span>
      <span class="s">Write-Host "Setting the prerelease version number variable to '$prereleaseVersionNumber'."</span>
      <span class="s">Write-Host "##vso[task.setvariable variable=prereleaseVersionNumber]$prereleaseVersionNumber"</span>
</code></pre></div></div>

<p>With the above, you can do things like determine whether to use the <code class="language-plaintext highlighter-rouge">versionNumber</code> or the <code class="language-plaintext highlighter-rouge">prereleaseVersionNumber</code> variables depending on if the <code class="language-plaintext highlighter-rouge">$(Build.SourceBranchName)</code> is the default branch (e.g. <code class="language-plaintext highlighter-rouge">main</code> or <code class="language-plaintext highlighter-rouge">master</code>) or a feature branch.
The below example shows one way of how to do this, and sets the <code class="language-plaintext highlighter-rouge">versionNumber</code> variable to the <code class="language-plaintext highlighter-rouge">stableVersionNumber</code> if building the <code class="language-plaintext highlighter-rouge">main</code> branch, or to the <code class="language-plaintext highlighter-rouge">prereleaseVersionNumber</code> if building any other branch.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">version.MajorMinor</span><span class="pi">:</span> <span class="s1">'</span><span class="s">1.0'</span> <span class="c1"># Manually adjust the version number as needed for semantic versioning. Patch is auto-incremented.</span>
  <span class="na">version.Patch</span><span class="pi">:</span> <span class="s">$[counter(variables['version.MajorMinor'], 0)]</span>
  <span class="na">stableVersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">$(version.MajorMinor).$(version.Patch)'</span>
  <span class="na">prereleaseVersionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span>
  <span class="na">versionNumber</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Set</span><span class="nv"> </span><span class="s">dynamically</span><span class="nv"> </span><span class="s">below</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">task'</span> <span class="c1"># Will be set to the stableVersionNumber or prereleaseVersionNumber based on the branch.</span>
  <span class="na">isMainBranch</span><span class="pi">:</span> <span class="s">$[eq(variables['Build.SourceBranch'], 'refs/heads/main')]</span> <span class="c1"># Determine if we're building the 'main' branch or not.</span>

<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the prereleaseVersionNumber variable value</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $dateTime = (Get-Date -Format 'yyyyMMddTHHmmss')</span>
      <span class="s">[string] $prereleaseVersionNumber = "$(stableVersionNumber)-ci$dateTime+$(Build.SourceVersion)"</span>
      <span class="s">Write-Host "Setting the prerelease version number variable to '$prereleaseVersionNumber'."</span>
      <span class="s">Write-Host "##vso[task.setvariable variable=prereleaseVersionNumber]$prereleaseVersionNumber"</span>

<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the versionNumber to the stable or prerelease version number based on if the 'main' branch is being built or not</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[bool] $isMainBranch = $$(isMainBranch)</span>
      <span class="s">[string] $versionNumber = "$(prereleaseVersionNumber)"</span>
      <span class="s">if ($isMainBranch)</span>
      <span class="s">{</span>
        <span class="s">$versionNumber = "$(stableVersionNumber)"</span>
      <span class="s">}</span>
      <span class="s">Write-Host "Setting the version number to use to '$versionNumber'."</span>
      <span class="s">Write-Host "##vso[task.setvariable variable=versionNumber]$versionNumber"</span>

<span class="pi">-</span> <span class="na">task</span><span class="pi">:</span> <span class="s">PowerShell@2</span>
  <span class="na">displayName</span><span class="pi">:</span> <span class="s">Set the name of the build (i.e. the Build.BuildNumber)</span>
  <span class="na">inputs</span><span class="pi">:</span>
    <span class="na">targetType</span><span class="pi">:</span> <span class="s1">'</span><span class="s">inline'</span>
    <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">[string] $buildName = "$(versionNumber)_$(Build.SourceBranchName)"</span>
      <span class="s">Write-Host "Setting the name of the build to '$buildName'."</span>
      <span class="s">Write-Host "##vso[build.updatebuildnumber]$buildName"</span>
</code></pre></div></div>

<p>You can also leverage <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#conditionally-set-a-task-input">expressions</a> to determine at runtime what inputs to provide to later tasks (e.g. the <code class="language-plaintext highlighter-rouge">stableVersionNumber</code> or the <code class="language-plaintext highlighter-rouge">prereleaseVersionNumber</code>), or if a tasks should run at all by placing a <a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/conditions?view=azure-devops&amp;tabs=yaml">condition</a> on it.</p>

<p>If you like you could combine all 3 PowerShell tasks into a single task for brevity.
I prefer to keep them separated for clarity.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Yaml builds are the future of Azure Pipelines.
I enjoy them because you get your build definition stored in source control with your code, it’s easy to copy-paste the yaml to other projects (you can also use yaml templates, a topic for another post ;) ), and it makes showing off examples in blogs and gists easier.</p>

<p>With so many great reasons to start using yaml for your Azure Pipelines builds, I hope this information helps you get your version numbers and build names setup how you like them.</p>

<p>I’d also like to throw a shout out to <a href="https://www.andrewhoefling.com/Blog/Post/azure-pipelines-custom-build-numbers-in-yaml-templates">Andrew Hoefling’s blog post</a> that introduced me to the <code class="language-plaintext highlighter-rouge">counter</code> function and helped me get started with using custom version numbers in my yaml builds.</p>

<p>Happy versioning!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Azure DevOps" /><category term="Azure Pipelines" /><category term="Build" /><category term="Azure DevOps" /><category term="Azure Pipelines" /><category term="Build" /><category term="Continuous Integration" /><category term="Versioning" /><category term="Semantic Version" /><category term="Prerelease" /><summary type="html"><![CDATA[I’m a fan of semantic versioning, especially for software meant to be consumed by other developers, such as NuGet packages. If you want to use semantic versioning however, it means you need to have some control over the version number that gets assigned to your software by your build system. Whether you are looking to use semantic versioning, or want to use some other version number format, in this post we will look at how to accomplish that when using yaml files for you Azure Pipeline.]]></summary></entry><entry><title type="html">Awesome Azure DevOps resources</title><link href="https://blog.danskingdom.com/Awesome-Azure-DevOps-resources/" rel="alternate" type="text/html" title="Awesome Azure DevOps resources" /><published>2019-12-09T00:00:00+00:00</published><updated>2019-12-09T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Awesome-Azure-DevOps-resources</id><content type="html" xml:base="https://blog.danskingdom.com/Awesome-Azure-DevOps-resources/"><![CDATA[<p>I’m a huge fan of Microsoft’s <a href="https://azure.microsoft.com/en-ca/services/devops/">Azure DevOps</a>.
As a developer, it’s a single place that allows me to do everything.
It stores my code, tracks my project ideas and current work, builds and deploys my applications, stores my artifacts, and more.
And the best part, <a href="https://azure.microsoft.com/en-ca/pricing/details/devops/azure-devops-services/">it’s all free</a> (well, if you’re a team of 5 people or less).
Typically I store my open source repositories on GitHub and my closed source projects in Azure DevOps.
That’s not a problem though, because Azure DevOps has awesome integration with GitHub, and again, most of the services are free when used with open source projects.
I’m grateful that we also use Azure DevOps at my organization, so I’m able to leverage all of the awesomeness in my day-to-day job as well.
Sure, it may not be perfect, but the teams are improving it all the time, and continually asking for community feedback.</p>

<p>With that said, here’s a quick list of some of my favourite Azure DevOps resources to help me stay up-to-date on the constantly evolving product:</p>

<ul>
  <li><a href="https://azuredevopslabs.com">Azure DevOps Labs</a>: Tons of labs/tutorials showing at-a-glance many of the things Azure DevOps can do.
You can then drill into the ones that interest you for step-by-step instructions to accomplish your task, or to just learn more about the features that Azure DevOps offers.</li>
  <li><a href="https://docs.microsoft.com/en-us/azure/devops/release-notes/features-timeline">Official features timeline</a>: The past features that Azure DevOps has delivered, the versions they went into, as well as the short(ish)-term roadmap of the larger features they’re planning to deliver.
This is great because it allows you to see if any of your wishlist items are on their short-term roadmap.</li>
  <li><a href="https://docs.microsoft.com/en-us/azure/devops/release-notes">Official release notes</a>: On the same parent page as the link above, here you can see all of the features that Azure DevOps has delivered on in past sprints.
Be sure to bookmark this page to stay up-to-date on all the new goodness that comes out every few weeks, as sometimes amazing enhancements get released under the radar.
This page will help you stay current so you don’t miss critical updates.</li>
  <li><a href="http://radiotfs.com">Radio TFS podcast</a>: A frequent podcast that talks about all things DevOps.
I’m personally not a big podcast guy, and have only ever listened to one or two of the talks.
I frequent this site for the links that get posted from the talks.
There’s always links to great blog posts and other resources.
It’s a great place to find out about community driven projects and non-Microsoft things that integrate with Azure DevOps.</li>
  <li><a href="https://devblogs.microsoft.com/devops/">Azure DevOps team official blog</a>: This is another awesome resource to find out about changes recently implemented or coming down the pipe for Azure DevOps, as well as other community projects and blog posts.</li>
  <li><a href="https://channel9.msdn.com/Shows/DevOps-Lab/">Channel 9 DevOps-Lab</a>: If you’re more into watching videos than reading blogs, this is a great channel to subscribe to.
It focuses on performing DevOps operations using Microsoft technologies; mostly, but not entirely limited to, Azure DevOps.</li>
  <li><a href="https://www.youtube.com/channel/UC-ikyViYMM69joIAv7dlMsA">YouTube DevOps channel</a>: Another great channel to subscribe to for videos.
This one actually includes the Channel 9 video feed mentioned above, as well as many other DevOps related videos; not all of them are about Azure DevOps.</li>
  <li>
    <p><a href="https://developercommunity.visualstudio.com/spaces/21/index.html">Official community feedback page</a>: The Developer Community is the official place to file bug reports and make feature requests for Azure DevOps.
In addition to filing new reports, you can also browse and up-vote existing ones.
I’ve used this service many times to get help with issues I’ve had, and it’s cool to see some of the other feature requests I didn’t know I wanted until reading about them.
You can also navigate here directly from your Azure DevOps page by accessing the Help section.</p>

    <p><img src="/assets/Posts/2019-12-09-Awesome-Azure-DevOps-resources/AzureDevOpsSubmitBugOrSuggestionScreenshot.png" alt="AzureDevOpsBugAndSuggestionScreenshot" /></p>
  </li>
  <li><a href="https://twitter.com/azuredevops">@AzureDevOps</a>: Following @AzureDevOps on Twitter is another great way to learn about cool new features.
The best part is, they’re interactive!
If you’re not sure about something or have a question, send them a tweet and they’ll try to help you out.
I’ve tweeted them many times in the past, and more often than not I get a response back within a day or two.
There are also many other noteworthy people on Twitter, such as the members that make up the <a href="https://twitter.com/hashtag/loecda">#LoECDA</a>; it would be too hard to list them all and keep it current.</li>
</ul>

<p>Do you have any other great Azure DevOps resources? Let me know in the comments!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Azure DevOps" /><category term="Azure DevOps" /><summary type="html"><![CDATA[I’m a huge fan of Microsoft’s Azure DevOps. As a developer, it’s a single place that allows me to do everything. It stores my code, tracks my project ideas and current work, builds and deploys my applications, stores my artifacts, and more. And the best part, it’s all free (well, if you’re a team of 5 people or less). Typically I store my open source repositories on GitHub and my closed source projects in Azure DevOps. That’s not a problem though, because Azure DevOps has awesome integration with GitHub, and again, most of the services are free when used with open source projects. I’m grateful that we also use Azure DevOps at my organization, so I’m able to leverage all of the awesomeness in my day-to-day job as well. Sure, it may not be perfect, but the teams are improving it all the time, and continually asking for community feedback.]]></summary></entry><entry><title type="html">Ignite 2019 mass session download script</title><link href="https://blog.danskingdom.com/Ignite-2019-mass-session-download-script/" rel="alternate" type="text/html" title="Ignite 2019 mass session download script" /><published>2019-11-08T00:00:00+00:00</published><updated>2019-11-08T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Ignite-2019-mass-session-download-script</id><content type="html" xml:base="https://blog.danskingdom.com/Ignite-2019-mass-session-download-script/"><![CDATA[<p>The Microsoft Ignite 2019 conference has ended, and you know what that means.
Time to mass download all the sessions you missed!</p>

<p>In previous years you’d have to seek out a community developed script; this year though, Microsoft is providing it for you :)</p>

<p>If you look below any of the session videos, such as <a href="https://myignite.techcommunity.microsoft.com/sessions/81591?source=schedule">Scott Hanselman’s Dev Keynote</a>, you’ll notice a link that hasn’t been there in previous years.
The <a href="https://myignite.techcommunity.microsoft.com/Download-Resources.zip">Get the bulk session resource download script</a> link.</p>

<p><img src="/assets/Posts/2019-11-08-Ignite-2019-mass-session-download-script/IgniteApplicationDevKeynoteScreenshot.png" alt="Screenshot" /></p>

<p>In case that download link disappears in the future, you can also <a href="/assets/Posts/2019-11-08-Ignite-2019-mass-session-download-script/Download-Resources.zip">grab it directly from me here</a>.</p>

<p>The nice thing about this script is it will also download the session’s slide deck if it exists.
You can either download EVERY session, or use the <code class="language-plaintext highlighter-rouge">-sessionCodes</code> command-line parameter to specify just the IDs of the sessions you want to download.
There’s a readme file included in the zip that specifies how the script can be used.
All of the sessions and their IDs <a href="https://myignite.techcommunity.microsoft.com/sessions">can be found on the session catalog here</a>.</p>

<p>For example, this is the PowerShell command I used to download just the sessions that I’m interested in and wasn’t able to attend (because there was too much other awesome content at the same time).</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\Download-Resources.ps1</span><span class="w"> </span><span class="nt">-directory</span><span class="w"> </span><span class="nx">C:\Temp</span><span class="w"> </span><span class="nt">-sessionCodes</span><span class="w"> </span><span class="s2">"BRK3064,BRK2203,BRK3066,POWA10,BRK2076,BRK2001,BRK2046,TK05,BRK2375,BRK2075,BRK2166,BRK3119,BRK3176,BRK3098,BRK2077,OPS40,MOD50,BRK2377,BRK2074,BRK4006,BRK3318,BRK3316"</span><span class="w">
</span></code></pre></div></div>

<p>Happy learning!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Learning" /><category term="Ignite" /><category term="Learning" /><summary type="html"><![CDATA[The Microsoft Ignite 2019 conference has ended, and you know what that means. Time to mass download all the sessions you missed!]]></summary></entry><entry><title type="html">Great Xamarin.Forms workshop from Ignite 2019</title><link href="https://blog.danskingdom.com/Great-Xamarin-Forms-workshop-from-Ignite-2019/" rel="alternate" type="text/html" title="Great Xamarin.Forms workshop from Ignite 2019" /><published>2019-11-03T00:00:00+00:00</published><updated>2019-11-03T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Great-Xamarin-Forms-workshop-from-Ignite-2019</id><content type="html" xml:base="https://blog.danskingdom.com/Great-Xamarin-Forms-workshop-from-Ignite-2019/"><![CDATA[<p>I’m lucky enough to attend the Microsoft Ignite 2019 conference this year, and do a pre-conference workshop.
The workshop I chose to do was <code class="language-plaintext highlighter-rouge">Build your first iOS and Android app with C#, Xamarin, and Azure</code> presented by James Montemagno.</p>

<p>While I had to pay for the interactive hands-on experience, James was awesome enough to provide <a href="https://github.com/jamesmontemagno/xamarin.forms-workshop">the workshop for free on his GitHub repo</a>.
So if you are new to Xamarin or haven’t done it in a while, be sure to check out the repo and run through the exercises.
He provides a <code class="language-plaintext highlighter-rouge">Start</code> directory for you to start from, and a <code class="language-plaintext highlighter-rouge">Finish</code> directory containing what the finished product should look like after you’ve ran through each exercise part.
James’ also mentioned that he’ll be uploading the slide deck he ran through to the GitHub repo as well.</p>

<p>Before starting on the workshop, you may want to run through <a href="https://dotnet.microsoft.com/learn/xamarin/hello-world-tutorial/intro">this Xamarin setup</a> to get your machine configured to run the Android emulator so you can easily build and debug your Xamarin apps.</p>

<h2 id="my-personal-notes">My personal notes</h2>

<p>In addition to the setup guide and James’ workshop, here are some tidbits I took from things James said that aren’t in the slides or workshop.</p>

<ul>
  <li><a href="https://www.syncfusion.com">SyncFusion</a>, Telerik, Infragistics, DevExpress, ComponentOne, and Steema for cross platform and Xamarin compatible controls.
SyncFusion offers <a href="https://www.syncfusion.com/products/communitylicense">a free community license</a>.</li>
  <li>Use <a href="https://app.quicktype.io/">https://app.quicktype.io/</a> for intelligently converting json to classes for many libraries.</li>
  <li>When in a .xaml file you can open the Toolbox window to see all of the available native controls to use, and drag and drop them in to get the boilerplate code for the control.</li>
  <li>You can use <a href="https://www.macincloud.com">MacInCloud</a> to rent a mac in the cloud for building and debugging on iOS.</li>
  <li><a href="https://github.com/jamesmontemagno/xamarin.forms-workshop/tree/master/Part%201%20-%20Displaying%20Data#displaying-data-1">The <code class="language-plaintext highlighter-rouge">x:DataType</code> attribute</a> in XAML is what provides compile-time checking for attributes specified in the XAML bindings.</li>
  <li>Use <a href="https://github.com/jamesmontemagno/xamarin.forms-workshop/tree/master/Part%202%20-%20MVVM%20%26%20Data%20Binding#build-the-monkeys-user-interface">the <code class="language-plaintext highlighter-rouge">ContentPage.BindingContext</code> attribute</a> to get intellisense on binding properties.</li>
  <li><a href="https://github.com/jamesmontemagno/mvvm-helpers">James’ Xamarin MVVM helpers library</a>.</li>
  <li>Can use <code class="language-plaintext highlighter-rouge">&lt;BoxView HeightRequest="1" Color="#DDDDDD"/&gt;</code> to create small horizontal line separators.</li>
  <li>Can use <a href="https://github.com/jamesmontemagno/xamarin.forms-workshop/tree/master/Part%203%20-%20Navigation#create-detailspagexaml-ui">NuGet package for circular images</a>.</li>
  <li>The ListView has a poor default caching strategy (for legacy reasons) <a href="https://github.com/jamesmontemagno/xamarin.forms-workshop/tree/master/Part%205%20-%20Pull%20To%20Refresh%20%26%20ListView%20Optimizations#caching-strategy">that should be set for better performance</a>.</li>
  <li>Can use the <code class="language-plaintext highlighter-rouge">Analytics.TrackEvent</code> to automatically track events to the App Center.</li>
  <li>Can use the <code class="language-plaintext highlighter-rouge">Crashes</code> class to see things like if the app crashed on the last run, log errors, and much more.</li>
  <li>Can use the <code class="language-plaintext highlighter-rouge">Data</code>class to easily store and retrieve data from your Azure Cosmos DB (still in preview, and not free yet, but they may have a free data tier of some sort when it comes out of preview).
It does local offline caching for you for free.</li>
  <li><a href="https://github.com/xamarin/XamarinComponents">https://github.com/xamarin/XamarinComponents</a> has some xamarin plugins for things like data caching and database plugins and others.</li>
</ul>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Xamarin" /><category term="CSharp" /><category term="Xamarin" /><category term="CSharp" /><category term="Ignite" /><summary type="html"><![CDATA[I’m lucky enough to attend the Microsoft Ignite 2019 conference this year, and do a pre-conference workshop. The workshop I chose to do was Build your first iOS and Android app with C#, Xamarin, and Azure presented by James Montemagno.]]></summary></entry><entry><title type="html">You don’t scale, so don’t be a hero</title><link href="https://blog.danskingdom.com/You-do-not-scale-so-do-not-be-a-hero/" rel="alternate" type="text/html" title="You don’t scale, so don’t be a hero" /><published>2019-10-26T00:00:00+00:00</published><updated>2019-10-27T00:00:00+00:00</updated><id>https://blog.danskingdom.com/You-do-not-scale-so-do-not-be-a-hero</id><content type="html" xml:base="https://blog.danskingdom.com/You-do-not-scale-so-do-not-be-a-hero/"><![CDATA[<p>I’ve been writing code since 1999; professionally since 2009.
For many years I put in 60+ hours a week at my job.
I was young.
I was committed.
And most importantly, I <strike>was</strike> am passionate about programming.</p>

<p>Putting in extra time didn’t really seem like a big deal.
I was doing what I loved to do, while helping my company at the same time.
I’m a problem solver.
I work on problems in my spare time, and research solutions.
I enjoy staying up-to-date on new technology.
My brain literally does not let me disconnect some days (to my wife’s dismay).
While I would love to focus on personal project problems after hours, my work ethic and sense of responsibility often points me toward focusing on work related problems instead.
And because I’m often online after hours already, responding to alerts or conversations that popped up after hours just happened naturally.</p>

<p>If this sounds anything like you, beware.
In my years, I’ve been known as the office hero, and have won awards to prove it!
The guy you come to when you don’t know where else to go.
The guy where even if I can’t solve your problem, I can likely direct you where to go; and if I can’t, I’ll help you find out who to go to.
This has all worked out well and good…..for me…..so far.</p>

<p>The problem is that I am not scalable; I’m just one person.</p>

<p><img src="/assets/Posts/2019-10-26-You-do-not-scale-so-do-not-be-a-hero/Clones.gif" alt="I don't scale" /></p>

<p>As our organization has grown, so too did the demands on my time.
Over the years, what used to be one or two interruptions a day grew to 10+.
Whenever somebody interrupts you it results in a context switch, which even if they just need your attention for 2 minutes, <a href="https://www.petrikainulainen.net/software-development/processes/the-cost-of-context-switching/">have been shown to reduce your productivity</a> by much more.
Even worse, what if they need something urgently after hours;
you’ve become the go-to person so guess who they’re calling on that Sunday afternoon.</p>

<p>When you become that go-to person, it’s not good for you or the company.
Don’t get me wrong, firefighters will always be needed;
Those employees who you call in a pinch to get you out of a tough spot.
The point is though, that they shouldn’t be relied upon.
They should be the exception, not the expectance.</p>

<p>A coworker of mine once put it very nicely. I can’t remember the exact wording, but it went something like:</p>

<blockquote>
  <p>Unknown to you, your child is playing in the street and there’s a car speeding toward them.
Luckily a stranger (the hero) happens to be walking by grabs the child and pulls him off the street just in time.</p>

  <p>Are you simply going to rely on a stranger being there again the next time?</p>
</blockquote>

<p>I would hope your answer to that question is “No”.
However, every time you help somebody out with something that you are not directly accountable for, or with something after hours when you are supposed to be off, you’re implicitly say “Yes”.</p>

<p><img src="/assets/Posts/2019-10-26-You-do-not-scale-so-do-not-be-a-hero/ThisIsFine.gif" alt="This is fine while everything is on fire" /></p>

<p>I’m not saying that you should let the child get hit by the proverbial car.
I’m saying that you should instead direct the people making the request to the appropriate team that is accountable for helping in the given situation.
If no team is accountable, then make a point to raise it up the chain of command to determine who <em>should</em> be accountable, and make sure that team knows they are accountable for it.
If that team is unavailable in the moment of a fire (such as on a weekend), then by all means help put the fire out, but make a point to follow up and ensure there’s a proper escalation policy in place for the next time it happens.
Many teams actually <em>get paid</em> to be responsive after hours, so you shouldn’t be consistently doing it out of the goodness of your heart (which I did for many years).</p>

<p>Similarly, maybe there’s not an urgent fire that needs to be put out, but something that needs to be completed on a tight deadline.
A project that your boss is hoping to have completed by Monday.
Or perhaps a critical issue has been discovered and you need to find and fix the problem, so you work a bunch of extra hours.
Or maybe you’re just very inspired by what you’re working on and put in a bunch of extra hours.</p>

<p>Putting in extra hours may be acceptable occasionally, and it may even reward you some well earned kudos and look good on a performance review.
However, <strong>if working late hours becomes the norm, it’s not good for you or the team</strong>.
It begins to look like your team is simply able to handle more work than it truly is.
At that point, it just becomes <em>you</em> masking a staffing problem.
If you are regularly putting in extra hours, you should be recording them and making sure to be compensated accordingly (e.g. extra pay or time off in lieu).</p>

<p>It’s in the post title, but I’ll state it again:</p>

<blockquote>
  <p>You don’t scale</p>
</blockquote>

<p>The best thing to do in the interest of <em>both you and your company</em> is to ensure that proper accountability is explicitly assigned, and that there are enough people to handle the workload.
If there aren’t enough people to handle the workload, then you need voice that and set proper expectations.</p>

<p>Hopefully by following my advice, you’ll be able to relax and enjoy your weekends, and the weekend fires will still get extinguished.</p>

<p><img src="/assets/Posts/2019-10-26-You-do-not-scale-so-do-not-be-a-hero/Relax.gif" alt="Relax" /></p>

<p>To recap, I’ve brushed over 3 types of developer heroism:</p>

<ul>
  <li>Being the office go-to person; either the helper or the local switchboard.
    <ul>
      <li>This is bad because it causes a lot context switching for you, which means lost productivity.</li>
    </ul>
  </li>
  <li>Putting in extra hours <em>all the time</em> without reporting them.
    <ul>
      <li>This is bad because it hides staffing capacity issues, and people may not realize when <em>you</em> are being overworked.</li>
    </ul>
  </li>
  <li>Making yourself available after-hours.
    <ul>
      <li>There will become an implicit expectation that you can drop whatever you’re doing jump in at anytime.
Make sure there’s an explicit escalation policy in place, and appropriate compensation.</li>
    </ul>
  </li>
</ul>

<p>You should be proud and feel good when you’re able to be the hero from time to time, but be very weary if it starts happening too often, and be proactive in finding and pushing for solutions to avoid it.</p>

<p>Happy <strike>coding</strike> helping :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Software Development" /><category term="Software Development" /><category term="Soft skills" /><summary type="html"><![CDATA[I’ve been writing code since 1999; professionally since 2009. For many years I put in 60+ hours a week at my job. I was young. I was committed. And most importantly, I was am passionate about programming.]]></summary></entry><entry><title type="html">Examples of setting up Serilog in .Net Core 3 Console apps and ASP .Net Core 3</title><link href="https://blog.danskingdom.com/Examples-of-setting-up-Serilog-in-Console-apps-and-ASP-Net-Core-3/" rel="alternate" type="text/html" title="Examples of setting up Serilog in .Net Core 3 Console apps and ASP .Net Core 3" /><published>2019-10-15T00:00:00+00:00</published><updated>2019-10-19T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Examples-of-setting-up-Serilog-in-Console-apps-and-ASP-Net-Core-3</id><content type="html" xml:base="https://blog.danskingdom.com/Examples-of-setting-up-Serilog-in-Console-apps-and-ASP-Net-Core-3/"><![CDATA[<p>It had been a while since I created a new console app from scratch, and all of my previous ones had been .Net Framework console apps, so when I needed to make one recently I decided I might as well go with a .Net Core 3 console app.
I’ve made minor changes in other .Net Core apps at work, and figured this was a good opportunity to get my hands dirty with it.</p>

<p>One of the very first things I do with any app I create is get logging setup.
Since I’d be using 2 new technologies, I decided to create a small Hello World type of console app, just to play around with getting things setup.</p>

<p>Rather than rehashing here what I’ve already documented, I’ll simply direct you to my <a href="https://github.com/deadlydog/Sample.Serilog">Sample.Serilog GitHub repository</a>.
The ReadMe explains what NuGet packages you’ll need to include to use Serilog and how to setup the configuration, as well as includes links to many other resources for more information.</p>

<p>The git repository contains a .sln with 3 projects that each show how to log to the console and a file:</p>

<ul>
  <li>A plain-jane .Net Core 3 console app that logs directly to Serilog without any abstractions.</li>
  <li>An ASP.Net Core 3 project that shows how to inject Serilog as the logger.</li>
  <li>A .Net Core 3 console app that uses <code class="language-plaintext highlighter-rouge">Microsoft.Extensions.Hosting</code> and <code class="language-plaintext highlighter-rouge">Microsoft.Extensions.DependencyInjection</code> to setup a DI container and inject Serilog as the logger into your classes.</li>
</ul>

<p>While creating my sample projects, I found a lot of documentation on getting Serilog setup for ASP.Net Core, but not very many for setting it up in a console app.
Also, most of the examples I found setup the sinks in code rather than a configuration file, which I consider bad practice.
Hopefully my GitHub repo provides a little more clarity in those areas with the documentation and examples.</p>

<h2 id="why-i-chose-serilog-for-logging">Why I chose Serilog for logging</h2>

<p>In the past with my .Net Framework apps I had defaulted to <a href="https://logging.apache.org/log4net/release/manual/introduction.html">log4net</a> for work projects (as it’s our standard at work) and <a href="https://nlog-project.org/">NLog</a> for personal projects (as I prefer its xml configuration syntax).
For a while now though I’ve been wanting to try out <a href="https://serilog.net/">Serilog</a>.
It’s main differentiator is that it is able to serialize objects when writing them in a log, so it will automatically log an instances public properties.</p>

<blockquote>
  <p>Update: Thanks to Rolf Kristensen for pointing out in the comments that <a href="https://github.com/NLog/NLog/wiki/How-to-use-structured-logging">NLog does indeed also provide this</a>.</p>
</blockquote>

<p>For example, if you have a simple class, like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">FirstName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">LastName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In a traditional logging solution like log4Net <strike>or NLog</strike>, if you wanted to have the <code class="language-plaintext highlighter-rouge">FirstName</code> and <code class="language-plaintext highlighter-rouge">LastName</code> properties written to the log sink (e.g. console, file, database, etc.) you would have to do something like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">()</span> <span class="p">{</span> <span class="n">FirstName</span> <span class="p">=</span> <span class="s">"Dan"</span><span class="p">;</span> <span class="n">LastName</span> <span class="p">=</span> <span class="s">"Schroeder"</span> <span class="p">}</span>
<span class="n">_logger</span><span class="p">.</span><span class="nf">Info</span><span class="p">(</span><span class="s">$"The persons First Name is '</span><span class="p">{</span><span class="n">person</span><span class="p">.</span><span class="n">FirstName</span><span class="p">}</span><span class="s">' and Last Name is '</span><span class="p">{</span><span class="n">person</span><span class="p">.</span><span class="n">LastName</span><span class="p">}</span><span class="s">'.)
</span></code></pre></div></div>

<p>Or override the <code class="language-plaintext highlighter-rouge">ToString()</code> method of the class to output the <code class="language-plaintext highlighter-rouge">FirstName</code> and <code class="language-plaintext highlighter-rouge">LastName</code>.</p>

<p>Using Serilog, we can instead do:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">()</span> <span class="p">{</span> <span class="n">FirstName</span> <span class="p">=</span> <span class="s">"Dan"</span><span class="p">;</span> <span class="n">LastName</span> <span class="p">=</span> <span class="s">"Schroeder"</span> <span class="p">}</span>
<span class="n">_logger</span><span class="p">.</span><span class="nf">Information</span><span class="p">(</span><span class="s">"The persons name is {@Person}."</span><span class="p">,</span> <span class="n">person</span><span class="p">);</span>
</code></pre></div></div>

<p>The output will look something like:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The persons name is {"FirstName": "Dan", "LastName": "Schroeder"}.
</code></pre></div></div>

<p>And of course the timestamp, log level, and other details can be added to show up automatically in the log message.</p>

<p>I thought that automatic serialization of objects was pretty clever and could be useful, so I thought I’d try it out.</p>

<h3 id="notable-serilog-aspects">Notable Serilog aspects</h3>

<p>The important things to note for Serilog are:</p>

<ol>
  <li>If you want objects to be serialized before being logged, you must prefix the template variable with an <code class="language-plaintext highlighter-rouge">@</code> or <code class="language-plaintext highlighter-rouge">$</code>. In the example above it’s <code class="language-plaintext highlighter-rouge">{@Person}</code>.</li>
  <li>For performance reasons, you don’t want to use string interpolation or concatenation in your log strings (called templates).
Instead you need to use the more traditional <code class="language-plaintext highlighter-rouge">string.Format(string format, params object[] args)</code> format.</li>
</ol>

<h2 id="other-logging-framework-samples">Other logging framework samples</h2>

<p>I have similar .Net Framework sample solution showing <code class="language-plaintext highlighter-rouge">log4net</code>, <code class="language-plaintext highlighter-rouge">NLog</code>, and the native <code class="language-plaintext highlighter-rouge">System.Diagnostics.Trace</code> logging frameworks in action in a private git repo.
If you think you would find seeing those sample applications useful, let me know in the comments and I’ll consider moving them into their own public GitHub repo, similar to my Serilog repo.</p>

<h2 id="source-code">Source code</h2>

<p>If you haven’t already, go checkout the <a href="https://github.com/deadlydog/Sample.Serilog">Sample.Serilog GitHub repository</a> for full examples of setting up Serilog in console and ASP.Net Core applications.
Fork it and play around with other sinks, enrichers, and configuration changes.
If you would like anything else documented, clarified, or shown, <a href="https://github.com/deadlydog/Sample.Serilog/issues">open an issue</a>.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="CSharp" /><category term="Serilog" /><category term="DotNet" /><category term="CSharp" /><category term="Serilog" /><category term="DotNet" /><category term="Logging" /><category term="Examples" /><summary type="html"><![CDATA[It had been a while since I created a new console app from scratch, and all of my previous ones had been .Net Framework console apps, so when I needed to make one recently I decided I might as well go with a .Net Core 3 console app. I’ve made minor changes in other .Net Core apps at work, and figured this was a good opportunity to get my hands dirty with it.]]></summary></entry><entry><title type="html">Create and test PowerShell Core cmdlets in C#</title><link href="https://blog.danskingdom.com/Create-and-test-PowerShell-Core-cmdlets-in-CSharp/" rel="alternate" type="text/html" title="Create and test PowerShell Core cmdlets in C#" /><published>2019-09-21T00:00:00+00:00</published><updated>2020-04-09T06:00:00+00:00</updated><id>https://blog.danskingdom.com/Create-and-test-PowerShell-Core-cmdlets-in-CSharp</id><content type="html" xml:base="https://blog.danskingdom.com/Create-and-test-PowerShell-Core-cmdlets-in-CSharp/"><![CDATA[<p>Creating PowerShell Core cmdlets in C# is actually quite easy, especially when you have a great blog post <a href="https://www.red-gate.com/simple-talk/dotnet/net-development/using-c-to-create-powershell-cmdlets-the-basics/">like this one</a> to reference.
There is also some official <a href="https://docs.microsoft.com/en-us/powershell/developer/cmdlet/how-to-write-a-simple-cmdlet">MS documentation</a> <a href="https://docs.microsoft.com/en-us/powershell/developer/module/how-to-write-a-powershell-binary-module">as well</a>.
What those posts don’t cover is unit testing your C# cmdlets.</p>

<p><strong>Update April, 2020:</strong> Microsoft released <a href="https://devblogs.microsoft.com/powershell/depending-on-the-right-powershell-nuget-package-in-your-net-project/">a new blog post</a> that describes the various PowerShell project types and which NuGet packages to use for each.
See that post for more detailed background information.</p>

<h2 id="creating-a-powershell-core-cmdlet">Creating a PowerShell Core cmdlet</h2>

<p>I’m not going to entirely rehash what’s in that awesome blog post, but I’ll share the highlights in case it goes offline in the future:</p>

<ol>
  <li>Create a new C# <code class="language-plaintext highlighter-rouge">Class Library (.Net Standard)</code> project in Visual Studio.</li>
  <li>Add the <a href="https://www.nuget.org/packages/PowerShellStandard.Library/">PowerShellStandard.Library NuGet package</a> to the project.</li>
  <li>Create a class for your cmdlet and have it inherit from the <code class="language-plaintext highlighter-rouge">System.Management.Automation.Cmdlet</code> class.</li>
  <li>Add a <code class="language-plaintext highlighter-rouge">Cmdlet</code> attribute to your class that describes the cmdlet verb and name.</li>
  <li>Add an <code class="language-plaintext highlighter-rouge">OutputType</code> attribute to your class that describes the type of object it returns (optional, but recommended).</li>
  <li>Put <code class="language-plaintext highlighter-rouge">Parameter</code> attributes on any input parameters your cmdlet uses.</li>
  <li>Override the <code class="language-plaintext highlighter-rouge">ProcessRecord</code> function to process each item in the pipeline, and optionally also the <code class="language-plaintext highlighter-rouge">BeginProcessing</code> (to do initialization), <code class="language-plaintext highlighter-rouge">EndProcessing</code> (to do finalization), and <code class="language-plaintext highlighter-rouge">StopProcessing</code> (to handle abnormal termination) functions.</li>
</ol>

<p>Here is an example of a minimal PowerShell Core cmdlet function:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Management.Automation</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">PowerShellCmdletInCSharpExample</span>
<span class="p">{</span>
    <span class="p">[</span><span class="nf">Cmdlet</span><span class="p">(</span><span class="n">VerbsCommon</span><span class="p">.</span><span class="n">Get</span><span class="p">,</span> <span class="s">"RepeatedPhrase"</span><span class="p">)]</span>
    <span class="p">[</span><span class="nf">OutputType</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="kt">string</span><span class="p">))]</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">GetRepeatedPhraseCmdlet</span> <span class="p">:</span> <span class="n">Cmdlet</span>
    <span class="p">{</span>
        <span class="p">[</span><span class="nf">Parameter</span><span class="p">(</span><span class="n">Position</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span> <span class="n">Mandatory</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">ValueFromPipeline</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">ValueFromPipelineByPropertyName</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
        <span class="p">[</span><span class="nf">Alias</span><span class="p">(</span><span class="s">"Word"</span><span class="p">)]</span>
        <span class="p">[</span><span class="nf">ValidateNotNullOrEmpty</span><span class="p">()]</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="n">Phrase</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

        <span class="p">[</span><span class="nf">Parameter</span><span class="p">(</span><span class="n">Position</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span> <span class="n">Mandatory</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span> <span class="n">ValueFromPipelineByPropertyName</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
        <span class="p">[</span><span class="nf">Alias</span><span class="p">(</span><span class="s">"Repeat"</span><span class="p">)]</span>
        <span class="k">public</span> <span class="kt">int</span> <span class="n">NumberOfTimesToRepeatPhrase</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>

        <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">ProcessRecord</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="k">base</span><span class="p">.</span><span class="nf">ProcessRecord</span><span class="p">();</span>

            <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringBuilder</span><span class="p">();</span>
            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">NumberOfTimesToRepeatPhrase</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
            <span class="p">{</span>
                <span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="n">Phrase</span><span class="p">);</span>
            <span class="p">}</span>

            <span class="nf">WriteObject</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="nf">ToString</span><span class="p">());</span> <span class="c1">// This is what actually "returns" output.</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="testing-your-powershell-core-cmdlet-in-c-with-xunit">Testing your PowerShell Core cmdlet in C# with xUnit</h2>

<p>The information I had trouble tracking down was how to unit test my PowerShell Core C# cmdlets.
Well, actually, that’s not exactly true.
As I pointed out in <a href="https://stackoverflow.com/questions/56696574/how-to-unit-test-a-powershell-core-binary-cmdlet-in-c-sharp">my Stack Overflow question</a>, I found several blog posts describing how to unit test C# PowerShell Core cmdlets. The problem was, none of the methods they described worked :(.</p>

<p>Most of the posts I found said to use the <code class="language-plaintext highlighter-rouge">PowerShellLibrary.Standard</code> NuGet package in your test project for your testing, which made sense since that’s the NuGet package we use in the real project.
However, I discovered that invoking the PowerShell Core cmdlet with that NuGet package always resulted in exceptions.
The solution (at least for .Net Core 2.2) is to instead use the <a href="https://www.nuget.org/packages/Microsoft.PowerShell.SDK/">Microsoft.PowerShell.SDK NuGet package</a> in your test project.</p>

<p>To create your test project:</p>

<ol>
  <li>Add a new C# <code class="language-plaintext highlighter-rouge">xUnit Test Project (.Net Core)</code> project to your solution.</li>
  <li>Include the <code class="language-plaintext highlighter-rouge">Microsoft.PowerShell.SDK</code> NuGet package in your test project.</li>
  <li>Add a reference to the project you created above where your cmdlet is defined.</li>
</ol>

<p>Now you can create a new class and call the <code class="language-plaintext highlighter-rouge">Invoke()</code> method on your cmdlet to exercise it.
PowerShell cmdlets have the option of returning multiple objects, so to make sure you read all of the cmdlet output, you’ll want to enumerate over all of the results.
One example of how you might do this could be:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">results</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;();</span>
<span class="kt">var</span> <span class="n">enumerator</span> <span class="p">=</span> <span class="n">cmdlet</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">().</span><span class="nf">GetEnumerator</span><span class="p">();</span>
<span class="k">while</span> <span class="p">(</span><span class="n">enumerator</span><span class="p">.</span><span class="nf">MoveNext</span><span class="p">())</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="n">enumerator</span><span class="p">.</span><span class="n">Current</span> <span class="k">as</span> <span class="kt">string</span><span class="p">;</span>
    <span class="n">results</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This works, but is a bit verbose to include in every test.
Luckily during a code review, a co-worker pointed out that I could condense that code down into the following:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">results</span> <span class="p">=</span> <span class="n">cmdlet</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">().</span><span class="n">OfType</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;().</span><span class="nf">ToList</span><span class="p">();</span>
</code></pre></div></div>

<p>That’s much more condensed and readable.
Here is an example of a full xUnit test that ensures a cmdlet returns back the expected string:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Fact</span><span class="p">]</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">ShouldReturnThePhraseRepeatedTheCorrectNumberOfTimes</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// Arrange.</span>
    <span class="kt">var</span> <span class="n">phrase</span> <span class="p">=</span> <span class="s">"A test phrase."</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">numberOfTimesToRepeat</span> <span class="p">=</span> <span class="m">3</span><span class="p">;</span>
    <span class="kt">var</span> <span class="n">cmdlet</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">GetRepeatedPhraseCmdlet</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">Phrase</span> <span class="p">=</span> <span class="n">phrase</span><span class="p">,</span>
        <span class="n">NumberOfTimesToRepeatPhrase</span> <span class="p">=</span> <span class="n">numberOfTimesToRepeat</span>
    <span class="p">};</span>
    <span class="kt">var</span> <span class="n">expectedResult</span> <span class="p">=</span> <span class="s">"A test phrase.A test phrase.A test phrase."</span><span class="p">;</span>

    <span class="c1">// Act.</span>
    <span class="kt">var</span> <span class="n">results</span> <span class="p">=</span> <span class="n">cmdlet</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">().</span><span class="n">OfType</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;().</span><span class="nf">ToList</span><span class="p">();</span>

    <span class="c1">// Assert.</span>
    <span class="n">Assert</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="n">results</span><span class="p">.</span><span class="nf">First</span><span class="p">(),</span> <span class="n">expectedResult</span><span class="p">);</span>
    <span class="n">Assert</span><span class="p">.</span><span class="nf">True</span><span class="p">(</span><span class="n">results</span><span class="p">.</span><span class="n">Count</span> <span class="p">==</span> <span class="m">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here is another example that ensures another cmdlet returns back the expected string array:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Fact</span><span class="p">]</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">ShouldReturnThePhraseRepeatedTheCorrectNumberOfTimes</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// Arrange.</span>
    <span class="kt">var</span> <span class="n">phrase</span> <span class="p">=</span> <span class="s">"A test phrase."</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">numberOfTimesToRepeat</span> <span class="p">=</span> <span class="m">3</span><span class="p">;</span>
    <span class="kt">var</span> <span class="n">cmdlet</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">GetRepeatedPhraseCollectionCmdlet</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">Phrase</span> <span class="p">=</span> <span class="n">phrase</span><span class="p">,</span>
        <span class="n">NumberOfTimesToRepeatPhrase</span> <span class="p">=</span> <span class="n">numberOfTimesToRepeat</span>
    <span class="p">};</span>
    <span class="kt">var</span> <span class="n">expectedResult</span> <span class="p">=</span> <span class="n">Enumerable</span><span class="p">.</span><span class="nf">Repeat</span><span class="p">(</span><span class="n">phrase</span><span class="p">,</span> <span class="n">numberOfTimesToRepeat</span><span class="p">);</span>

    <span class="c1">// Act.</span>
    <span class="kt">var</span> <span class="n">results</span> <span class="p">=</span> <span class="n">cmdlet</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">().</span><span class="n">OfType</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;().</span><span class="nf">ToList</span><span class="p">();</span>

    <span class="c1">// Assert.</span>
    <span class="n">Assert</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="n">results</span><span class="p">,</span> <span class="n">expectedResult</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="downloadable-example">Downloadable example</h2>

<p>I’ve created <a href="https://github.com/deadlydog/PowerShellCmdletInCSharpExample">a small sample solution on GitHub</a> that you can check out.
It shows how to create a couple basic PowerShell Core cmdlets, as well as a test project to test both cmdlets using xUnit.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Hopefully you don’t fall into the same traps I did when trying to test my PowerShell Core cmdlets in C#.
In this post I’ve shown you the basics of what is needed to create PowerShell Core cmdlets, as well as how to test them.
I’ve also provided a sample app that you can poke around in for any details that I may have missed in this post.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="CSharp" /><category term="PowerShell" /><category term="Testing" /><category term="xUnit" /><category term="CSharp" /><category term="PowerShell" /><category term="Testing" /><category term="xUnit" /><summary type="html"><![CDATA[Creating PowerShell Core cmdlets in C# is actually quite easy, especially when you have a great blog post like this one to reference. There is also some official MS documentation as well. What those posts don’t cover is unit testing your C# cmdlets.]]></summary></entry><entry><title type="html">Catch and display unhandled exceptions in your WPF app</title><link href="https://blog.danskingdom.com/Catch-and-display-unhandled-exceptions-in-your-WPF-app/" rel="alternate" type="text/html" title="Catch and display unhandled exceptions in your WPF app" /><published>2019-09-18T00:00:00+00:00</published><updated>2019-09-18T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Catch-and-display-unhandled-exceptions-in-your-WPF-app</id><content type="html" xml:base="https://blog.danskingdom.com/Catch-and-display-unhandled-exceptions-in-your-WPF-app/"><![CDATA[<p>When creating a WPF application, one of the best things you can do upfront is add some code to catch any unhandled exceptions.
There are <a href="https://stackoverflow.com/a/1472562/602585">numerous ways unhandled exceptions can be caught</a>, and <a href="https://stackoverflow.com/a/46804709/602585">this Stack Overflow answer</a> shows how you can nicely handle them.
One thing I especially like about that answer is they also capture the application’s assembly name and version in the error message being logged, which can be helpful if there are multiple versions of your app in the wild.</p>

<p>I thought I would show a sample here though that handles the exceptions a bit differently, allowing the user to choose and try and keep the app alive when applicable, rather than it hard crashing.
This is what the <code class="language-plaintext highlighter-rouge">App.xaml.cs</code> file looks like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Diagnostics</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading.Tasks</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Windows</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Windows.Threading</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">WpfApplicationCatchUnhandledExceptionsExample</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">App</span> <span class="p">:</span> <span class="n">Application</span>
    <span class="p">{</span>
        <span class="k">public</span> <span class="nf">App</span><span class="p">()</span> <span class="p">:</span> <span class="k">base</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="nf">SetupUnhandledExceptionHandling</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="k">private</span> <span class="k">void</span> <span class="nf">SetupUnhandledExceptionHandling</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="c1">// Catch exceptions from all threads in the AppDomain.</span>
            <span class="n">AppDomain</span><span class="p">.</span><span class="n">CurrentDomain</span><span class="p">.</span><span class="n">UnhandledException</span> <span class="p">+=</span> <span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="p">=&gt;</span>
                <span class="nf">ShowUnhandledException</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">ExceptionObject</span> <span class="k">as</span> <span class="n">Exception</span><span class="p">,</span> <span class="s">"AppDomain.CurrentDomain.UnhandledException"</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>

            <span class="c1">// Catch exceptions from each AppDomain that uses a task scheduler for async operations.</span>
            <span class="n">TaskScheduler</span><span class="p">.</span><span class="n">UnobservedTaskException</span> <span class="p">+=</span> <span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="p">=&gt;</span>
                <span class="nf">ShowUnhandledException</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">Exception</span><span class="p">,</span> <span class="s">"TaskScheduler.UnobservedTaskException"</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>

            <span class="c1">// Catch exceptions from a single specific UI dispatcher thread.</span>
            <span class="n">Dispatcher</span><span class="p">.</span><span class="n">UnhandledException</span> <span class="p">+=</span> <span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span> <span class="p">=&gt;</span>
            <span class="p">{</span>
                <span class="c1">// If we are debugging, let Visual Studio handle the exception and take us to the code that threw it.</span>
                <span class="k">if</span> <span class="p">(!</span><span class="n">Debugger</span><span class="p">.</span><span class="n">IsAttached</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">args</span><span class="p">.</span><span class="n">Handled</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
                    <span class="nf">ShowUnhandledException</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">Exception</span><span class="p">,</span> <span class="s">"Dispatcher.UnhandledException"</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">};</span>

            <span class="c1">// Catch exceptions from the main UI dispatcher thread.</span>
            <span class="c1">// Typically we only need to catch this OR the Dispatcher.UnhandledException.</span>
            <span class="c1">// Handling both can result in the exception getting handled twice.</span>
            <span class="c1">//Application.Current.DispatcherUnhandledException += (sender, args) =&gt;</span>
            <span class="c1">//{</span>
            <span class="c1">//	// If we are debugging, let Visual Studio handle the exception and take us to the code that threw it.</span>
            <span class="c1">//	if (!Debugger.IsAttached)</span>
            <span class="c1">//	{</span>
            <span class="c1">//		args.Handled = true;</span>
            <span class="c1">//		ShowUnhandledException(args.Exception, "Application.Current.DispatcherUnhandledException", true);</span>
            <span class="c1">//	}</span>
            <span class="c1">//};</span>
        <span class="p">}</span>

        <span class="k">void</span> <span class="nf">ShowUnhandledException</span><span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">,</span> <span class="kt">string</span> <span class="n">unhandledExceptionType</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">promptUserForShutdown</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">messageBoxTitle</span> <span class="p">=</span> <span class="s">$"Unexpected Error Occurred: </span><span class="p">{</span><span class="n">unhandledExceptionType</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>
            <span class="kt">var</span> <span class="n">messageBoxMessage</span> <span class="p">=</span> <span class="s">$"The following exception occurred:\n\n</span><span class="p">{</span><span class="n">e</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>
            <span class="kt">var</span> <span class="n">messageBoxButtons</span> <span class="p">=</span> <span class="n">MessageBoxButton</span><span class="p">.</span><span class="n">OK</span><span class="p">;</span>

            <span class="k">if</span> <span class="p">(</span><span class="n">promptUserForShutdown</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">messageBoxMessage</span> <span class="p">+=</span> <span class="s">"\n\nNormally the app would die now. Should we let it die?"</span><span class="p">;</span>
                <span class="n">messageBoxButtons</span> <span class="p">=</span> <span class="n">MessageBoxButton</span><span class="p">.</span><span class="n">YesNo</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="c1">// Let the user decide if the app should die or not (if applicable).</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">MessageBox</span><span class="p">.</span><span class="nf">Show</span><span class="p">(</span><span class="n">messageBoxMessage</span><span class="p">,</span> <span class="n">messageBoxTitle</span><span class="p">,</span> <span class="n">messageBoxButtons</span><span class="p">)</span> <span class="p">==</span> <span class="n">MessageBoxResult</span><span class="p">.</span><span class="n">Yes</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="n">Application</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="nf">Shutdown</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As a best practice you should be logging these exceptions somewhere as well, such as to a file on the local machine or to a centralized logging storage mechanism, as shown in the Stack Overflow answer mentioned earlier.</p>

<p>Explicitly displaying the message to the user as well, as shown here, can be very helpful, as it then does not require the user to pull the error message out of some file on their machine, or for you to hunt down the users specific exception in a sea of other user exceptions (depending on what centralized logging mechanism you are using).
With this approach, the user can just do a <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy the message box text, or take a screenshot of the error (even with their phone camera!), and email it to you.</p>

<p>The message box may look a bit crude for professionally polished apps, but I think is fine for apps distributed internally in your organization.
Of course you could use some other UI mechanism to display the error to the user, and may want to change the message box message wording.
Also, I would hope that unhandled exceptions in your app are not very common, so they shouldn’t see the message box too often.</p>

<p>I’ll note though that the <code class="language-plaintext highlighter-rouge">TaskScheduler.UnobservedTaskException</code> exceptions often do not crash the app.
In fact, often times those exceptions will simply be lost and the app will silently keep on working (although maybe not properly).
For those errors, you may want to <em>just</em> log the exception and not display it to the user.</p>

<p>You may not want to display the exception stack trace to your users; maybe just because it’s ugly, or because you’re worried about them seeing some of your internal code function and parameter names.
That’s your call depending on the apps target audience and what you feel is best.</p>

<p>I’ve found this technique useful for some internal tools I’ve made, and thought I’d share.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="WPF" /><category term="CSharp" /><category term="WPF" /><category term="CSharp" /><category term="Exception" /><category term="Error" /><summary type="html"><![CDATA[When creating a WPF application, one of the best things you can do upfront is add some code to catch any unhandled exceptions. There are numerous ways unhandled exceptions can be caught, and this Stack Overflow answer shows how you can nicely handle them. One thing I especially like about that answer is they also capture the application’s assembly name and version in the error message being logged, which can be helpful if there are multiple versions of your app in the wild.]]></summary></entry><entry><title type="html">A better way to do TestCases when unit testing with Pester</title><link href="https://blog.danskingdom.com/A-better-way-to-do-TestCases-when-unit-testing-with-Pester/" rel="alternate" type="text/html" title="A better way to do TestCases when unit testing with Pester" /><published>2019-09-07T00:00:00+00:00</published><updated>2020-06-20T00:00:00+00:00</updated><id>https://blog.danskingdom.com/A-better-way-to-do-TestCases-when-unit-testing-with-Pester</id><content type="html" xml:base="https://blog.danskingdom.com/A-better-way-to-do-TestCases-when-unit-testing-with-Pester/"><![CDATA[<blockquote>
  <p>Note: Pester v5 was released which made breaking changes.
The code shown here works with Pester v4 and previous, but not v5.
I’m hoping to update this post in the future to show how to use this same technique with Pester v5.</p>
</blockquote>

<p>While writing some PowerShell code a while back I found myself at a crossroads in terms of the style I wanted to write some unit tests in with <a href="https://github.com/pester/Pester">Pester</a>.
I had a number of test cases that would be testing the same function, just with different input data.
In this post we take a look at a simple unit test example in Pester, and how we can evolve it to become better.</p>

<h2 id="a-simple-example-to-start-from">A simple example to start from</h2>

<p>Below is a shortened and simplified example of the tests I was writing.
There’s a <code class="language-plaintext highlighter-rouge">Get-WorkingDirectory</code> function that I wrote and want to test, and it takes 3 parameters: <code class="language-plaintext highlighter-rouge">workingDirectoryOption</code>, <code class="language-plaintext highlighter-rouge">customWorkingDirectory</code>, and <code class="language-plaintext highlighter-rouge">applicationPath</code>.
The function should return the custom directory or the application directory based on the <code class="language-plaintext highlighter-rouge">workingDirectoryOption</code> that was provided.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Describe</span><span class="w"> </span><span class="s1">'Get-WorkingDirectory'</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting the Application Directory as the working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the applications directory when no Custom Working Directory is given'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the applications directory when a Custom Working Directory is given'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting a custom working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the custom directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the custom directory even if its blank'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="s1">''</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The code above produces the following Pester output:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Describing Get-WorkingDirectory

  Context When requesting the Application Directory as the working directory
    [+] Returns the applications directory when no Custom Working Directory is given 98ms
    [+] Returns the applications directory when a Custom Working Directory is given 15ms

  Context When requesting a custom working directory
    [+] Returns the custom directory 67ms
    [+] Returns the custom directory even if its blank 15ms
</code></pre></div></div>

<p>Don’t worry about what the internals of the <code class="language-plaintext highlighter-rouge">Get-WorkingDirectory</code> function might look like, or the fact that <code class="language-plaintext highlighter-rouge">workingDirectoryOption</code> is a string rather than an enum/bool/switch, or that we could have separate <code class="language-plaintext highlighter-rouge">Get-CustomWorkingDirectory</code> and <code class="language-plaintext highlighter-rouge">Get-ApplicationWorkingDirectory</code> functions.
Those are all things that could be improved, but we’re not concerned with that for this post.</p>

<h2 id="use-a-function-for-the-assertion">Use a function for the assertion</h2>

<p>In the example above we only have 4 test cases, but in practice you may have 10s or 100s of test cases for a particular function.
Also, in the example above we’re able to exercise the function and assert the result in only 2 lines of code, but for other scenarios each test may require many lines to arrange, act, and assert.
That can cause your test files to quickly bloat from a lot of copy and pasting.
One common technique to help alleviate that is to use other functions for arranging and asserting.
Let’s do that here and see how the code transforms.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Describe</span><span class="w"> </span><span class="s1">'Get-WorkingDirectory'</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">function</span><span class="w"> </span><span class="nf">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="w">
        </span><span class="p">(</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
        </span><span class="p">)</span><span class="w">

        </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">
        </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting the Application Directory as the working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the applications directory when no Custom Working Directory is given'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w"> </span><span class="nt">-expectedWorkingDirectory</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the applications directory when a Custom Working Directory is given'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w"> </span><span class="nt">-expectedWorkingDirectory</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting a custom working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the custom directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w"> </span><span class="nt">-expectedWorkingDirectory</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the custom directory even if its blank'</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="w"> </span><span class="nt">-expectedWorkingDirectory</span><span class="w"> </span><span class="s1">''</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The code above produces the following Pester output:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Describing Get-WorkingDirectory

  Context When requesting the Application Directory as the working directory
    [+] Returns the applications directory when no Custom Working Directory is given 98ms
    [+] Returns the applications directory when a Custom Working Directory is given 15ms

  Context When requesting a custom working directory
    [+] Returns the custom directory 67ms
    [+] Returns the custom directory even if its blank 15ms
</code></pre></div></div>

<p>You can see that each test is now only a single line; one call to the <code class="language-plaintext highlighter-rouge">Assert-GetWorkingDirectoryReturnsCorrectResult</code> function.
The only difference between the tests are the parameters that they pass to the function.
The Pester output is identical.</p>

<h2 id="save-lines-of-code-by-using-testcases">Save lines of code by using <code class="language-plaintext highlighter-rouge">TestCases</code></h2>

<p>Most unit testing frameworks, including Pester, come with a way to call the same test function multiple times with different parameters, allowing the code to become even shorter.
Pester accomplishes this by allowing a <code class="language-plaintext highlighter-rouge">TestCases</code> parameter to be passed to the <a href="https://github.com/pester/Pester/wiki/It"><code class="language-plaintext highlighter-rouge">It</code> method</a>.
Here is what the code looks like after being refactored to use <code class="language-plaintext highlighter-rouge">TestCases</code>.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Describe</span><span class="w"> </span><span class="s1">'Get-WorkingDirectory'</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">It</span><span class="w"> </span><span class="s1">'Returns the correct working directory'</span><span class="w"> </span><span class="nt">-TestCases</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="p">@{</span><span class="w"> </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">@{</span><span class="w"> </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">@{</span><span class="w"> </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">@{</span><span class="w"> </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="w">
        </span><span class="p">(</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
        </span><span class="p">)</span><span class="w">

        </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">
        </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The code above produces the following Pester output:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Describing Get-WorkingDirectory
  [+] Returns the correct working directory 56ms
  [+] Returns the correct working directory 11ms
  [+] Returns the correct working directory 15ms
  [+] Returns the correct working directory 13ms
</code></pre></div></div>

<p>You can see that the 4 test cases are now expressed as an array of hashtables.
The hashtable defines the parameter values that should be used for the test.
You may have noticed they include an additional <code class="language-plaintext highlighter-rouge">expectedWorkingDirectory</code> parameter, which is used to perform the assertion.</p>

<h3 id="things-i-like-about-this-approach">Things I like about this approach</h3>

<ul>
  <li>Fewer lines of code required*.</li>
  <li>The 4 test cases are now stacked directly upon each other, making it visually easier to see the differences between that parameters used for each test case.</li>
</ul>

<p><em>* You may have noticed that I also removed the 2 <code class="language-plaintext highlighter-rouge">Context</code> statements.
If I would have kept them, the entire function would have needed to be copied, making the number of lines of code much longer.</em></p>

<h3 id="things-i-dont-like-about-this-approach">Things I don’t like about this approach</h3>

<ul>
  <li>In the code, I’ve lost the english description of what the test is actually testing.</li>
  <li>I’ve also lost it in the Pester test result’s output.</li>
</ul>

<p>This is actually a very big problem in my opinion.
Not having the english description means that when a tests fails, I don’t immediately have a clear idea of what test scenario is no longer working; I need to go digging through the test code to try and figure out which of the <code class="language-plaintext highlighter-rouge">TestCases</code> is failing.
Also, once I find which test case is failing, it may not be obvious what the test case is actually intending to test.
I can look at the parameters, but without any context I’m not sure which parameters are relevant.
Is the fact that one of the parameters is an empty string important?
Or maybe it’s that the working directory path has a special character in it?
Or maybe it has to do with the particular <code class="language-plaintext highlighter-rouge">workingDirectoryOption</code> value being provided?</p>

<p><strong>Having a clear description of what is being tested and what the expected result should be are vitally important</strong>. e.g. ‘Returns the applications directory when a Custom Working Directory is given’.</p>

<h2 id="a-hybrid-approach">A hybrid approach</h2>

<p>So we’ve seen how to use a function to perform the assertions, as well as how to define the test cases stacked on top of each other to easily compare all of the test cases being covered.
Let’s see if we can put them together to get the benefits of using <code class="language-plaintext highlighter-rouge">TestCases</code> without incurring the downsides.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Describe</span><span class="w"> </span><span class="s1">'Get-WorkingDirectory'</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">function</span><span class="w"> </span><span class="nf">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="w">
        </span><span class="p">(</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$testDescription</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="p">,</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
        </span><span class="p">)</span><span class="w">

        </span><span class="n">It</span><span class="w"> </span><span class="nv">$testDescription</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting the Application Directory as the working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="p">[</span><span class="n">hashtable</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$tests</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the applications directory when no Custom Working Directory is given'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the applications directory when a Custom Working Directory is given'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">)</span><span class="w">
        </span><span class="nv">$tests</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="p">[</span><span class="n">hashtable</span><span class="p">]</span><span class="w"> </span><span class="nv">$parameters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="w">
            </span><span class="n">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w"> </span><span class="err">@</span><span class="nx">parameters</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting a custom working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="p">[</span><span class="n">hashtable</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$tests</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the custom directory'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the custom directory even if its blank'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">)</span><span class="w">
        </span><span class="nv">$tests</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="p">[</span><span class="n">hashtable</span><span class="p">]</span><span class="w"> </span><span class="nv">$parameters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="w">
            </span><span class="n">Assert-GetWorkingDirectoryReturnsCorrectResult</span><span class="w"> </span><span class="err">@</span><span class="nx">parameters</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The code above produces the following Pester output:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Describing Get-WorkingDirectory

  Context When requesting the Application Directory as the working directory
    [+] Returns the applications directory when no Custom Working Directory is given 90ms
    [+] Returns the applications directory when no Custom Working Directory is given 9ms

  Context When requesting a custom working directory
    [+] Returns the custom directory 49ms
    [+] Returns the custom directory even if its blank 11ms
</code></pre></div></div>

<p>Notice that the <code class="language-plaintext highlighter-rouge">It</code> block was moved into the <code class="language-plaintext highlighter-rouge">Assert-GetWorkingDirectoryReturnsCorrectResult</code> function, and that the test cases now include an additional <code class="language-plaintext highlighter-rouge">testDescription</code> parameter.</p>

<p>We are no longer using the built-in <code class="language-plaintext highlighter-rouge">TestCases</code> functionality, but instead create our own hashtable array of test cases and manually loop through each of them and call the <code class="language-plaintext highlighter-rouge">Assert-GetWorkingDirectoryReturnsCorrectResult</code> function, splatting the test case parameters.
Ideally this additional code could be avoided if the native <code class="language-plaintext highlighter-rouge">TestCases</code> functionality supported providing the <code class="language-plaintext highlighter-rouge">It</code> description in the <code class="language-plaintext highlighter-rouge">TestCases</code> hashtable array.
I’ve submitted <a href="https://github.com/pester/Pester/issues/1361">a GitHub issue requesting this feature in Pester</a> to more easily get this functionality, but for now the approach shown here is the best I could think of.</p>

<blockquote>
  <p>Update: Since writing this post I’ve discovered a better Pester-native way that achieves the same results as this hybrid approach.
I’ve left the hybrid approach in here though for anybody interested, and describe the Pester-native approach next.</p>
</blockquote>

<p>This approach allows us to get the best of both worlds; we have contextual english descriptions of each test case, both in code and in the Pester output, while also having our test cases stacked on top of each other so we can easily compare the parameters for each one, and easily add new test cases with minimal code.</p>

<p>While the code in this example is actually longer than the other approaches we started with, that changes as more test cases are added.</p>

<p>I could have skipped the <code class="language-plaintext highlighter-rouge">Context</code> blocks altogether and just included their text in the <code class="language-plaintext highlighter-rouge">testDescription</code> which would shorten the code a bit.
However, the <code class="language-plaintext highlighter-rouge">workingDirectoryOption</code> parameter value fundamentally changes how the <code class="language-plaintext highlighter-rouge">Get-WorkingDirectory</code> function behaves, and having separate contexts makes that more clear.</p>

<h2 id="the-pester-native-approach">The Pester-native approach</h2>

<p>Since originally writing this post I’ve discovered that the <code class="language-plaintext highlighter-rouge">It</code> block supports variable substitution in its name when using the <code class="language-plaintext highlighter-rouge">TestCases</code> parameter.
They actually show it off on <a href="https://github.com/pester/Pester">the main Pester ReadMe page</a> with this example code:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">It</span><span class="w"> </span><span class="s2">"Given valid -Name '&lt;Filter&gt;', it returns '&lt;Expected&gt;'"</span><span class="w"> </span><span class="nt">-TestCases</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="p">@{</span><span class="w"> </span><span class="nx">Filter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Earth'</span><span class="p">;</span><span class="w"> </span><span class="nx">Expected</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Earth'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">@{</span><span class="w"> </span><span class="nx">Filter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ne*'</span><span class="w">  </span><span class="p">;</span><span class="w"> </span><span class="nx">Expected</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Neptune'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">@{</span><span class="w"> </span><span class="nx">Filter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ur*'</span><span class="w">  </span><span class="p">;</span><span class="w"> </span><span class="nx">Expected</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Uranus'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">@{</span><span class="w"> </span><span class="nx">Filter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'m*'</span><span class="w">   </span><span class="p">;</span><span class="w"> </span><span class="nx">Expected</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Mercury'</span><span class="p">,</span><span class="w"> </span><span class="s1">'Mars'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="nv">$Filter</span><span class="p">,</span><span class="w"> </span><span class="nv">$Expected</span><span class="p">)</span><span class="w">

    </span><span class="nv">$planets</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Planet</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$Filter</span><span class="w">
    </span><span class="nv">$planets</span><span class="o">.</span><span class="nf">Name</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="nv">$Expected</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>and its Pester output:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[+] Given valid -Name 'Earth', it returns 'Earth' 27ms
[+] Given valid -Name 'ne*', it returns 'Neptune' 16ms
[+] Given valid -Name 'ur*', it returns 'Uranus' 17ms
[+] Given valid -Name 'm*', it returns 'Mercury Mars' 15ms
</code></pre></div></div>

<p>While I did notice that documentation before, it never clicked that I could use it to achieve my desired functionality here.
So continuing with our earlier example, I could use the following code to give each of my test cases a rich, contextual description:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Describe</span><span class="w"> </span><span class="s1">'Get-WorkingDirectory'</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting the Application Directory as the working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'&lt;testDescription&gt;'</span><span class="w"> </span><span class="nt">-TestCases</span><span class="w"> </span><span class="p">@(</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the applications directory when no Custom Working Directory is given'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the applications directory when a Custom Working Directory is given'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'ApplicationDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="kr">param</span><span class="w">
            </span><span class="p">(</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="p">,</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="p">,</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="p">,</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
            </span><span class="p">)</span><span class="w">

            </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Context</span><span class="w"> </span><span class="s1">'When requesting a custom working directory'</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">It</span><span class="w"> </span><span class="s1">'&lt;testDescription&gt;'</span><span class="w"> </span><span class="nt">-TestCases</span><span class="w"> </span><span class="p">@(</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the custom directory'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\SomeDirectory'</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">@{</span><span class="w"> </span><span class="nx">testDescription</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Returns the custom directory even if its blank'</span><span class="w">
                </span><span class="nx">workingDirectoryOption</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'CustomDirectory'</span><span class="p">;</span><span class="w"> </span><span class="nx">customWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="p">;</span><span class="w"> </span><span class="nx">applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\AppDirectory\MyApp.exe'</span><span class="p">;</span><span class="w"> </span><span class="nx">expectedWorkingDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="kr">param</span><span class="w">
            </span><span class="p">(</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="p">,</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="p">,</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="p">,</span><span class="w">
                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
            </span><span class="p">)</span><span class="w">

            </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-WorkingDirectory</span><span class="w"> </span><span class="nt">-workingDirectoryOption</span><span class="w"> </span><span class="nv">$workingDirectoryOption</span><span class="w"> </span><span class="nt">-customWorkingDirectory</span><span class="w"> </span><span class="nv">$customWorkingDirectory</span><span class="w"> </span><span class="nt">-applicationPath</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">
            </span><span class="nv">$result</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="nv">$expectedWorkingDirectory</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The code above produces the following Pester output:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Describing Get-WorkingDirectory

  Context When requesting the Application Directory as the working directory
    [+] Returns the applications directory when no Custom Working Directory is given 90ms
    [+] Returns the applications directory when no Custom Working Directory is given 9ms

  Context When requesting a custom working directory
    [+] Returns the custom directory 49ms
    [+] Returns the custom directory even if its blank 11ms
</code></pre></div></div>

<p>So here you can see that we’ve replaced the name of the <code class="language-plaintext highlighter-rouge">It</code> blocks with <code class="language-plaintext highlighter-rouge">&lt;testDescription&gt;</code>, as that’s the name of the variable we provide in each test case hashtable.
For brevity you may decide to replace <code class="language-plaintext highlighter-rouge">testDescription</code> with <code class="language-plaintext highlighter-rouge">it</code>, or something similar.</p>

<p>Also, you’ll notice that we have a lot of duplicated code between the 2 <code class="language-plaintext highlighter-rouge">It</code> statements again, so you could refactor that out into a function like we did in the hybrid approach above.
The benefits that this has over the hybrid approach is that we don’t need to define the <code class="language-plaintext highlighter-rouge">[hashtable[]] $tests</code> variable any longer, nor do we need to manually iterate over it with the <code class="language-plaintext highlighter-rouge">$tests | ForEach-Object</code> statement, so this can save us from having to write that redundant code for every <code class="language-plaintext highlighter-rouge">It</code> block using <code class="language-plaintext highlighter-rouge">TestCases</code>.</p>

<h2 id="conclusion">Conclusion</h2>

<p>We started with a simple example and saw how to refactor it to use a an assertion function to make it less verbose when we have many tests.
We then saw how to refactor it to use <code class="language-plaintext highlighter-rouge">TestCases</code> to make it less verbose and easy to compare the test cases, at the cost of reduced clarity and context.
Next, we saw a hybrid approach that allows you to see all of the test cases side-by-side without losing important contextual information about the test cases.
Lastly, we saw how we can use the variable substitution functionality of the <code class="language-plaintext highlighter-rouge">It</code> block to achieve the same results as the hybrid approach, while saving on a bit of boilerplate code for every <code class="language-plaintext highlighter-rouge">It</code> block using <code class="language-plaintext highlighter-rouge">TestCases</code>.</p>

<p>There are always many different ways to do things when programming, and the approaches we choose often come down to personal preference, as well as other contextual information.
For example, if you only have a few test cases (as in the examples here), it may not be worth it to implement the approaches I’ve shown.
I hope that you’ll find the approaches I’ve presented here valuable, or at the very least, interesting.</p>

<p>Feel free to leave comments and let me know what you think, or perhaps ways these approaches could be improved.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Pester" /><category term="Unit Testing" /><category term="PowerShell" /><category term="Pester" /><category term="Unit Testing" /><summary type="html"><![CDATA[Note: Pester v5 was released which made breaking changes. The code shown here works with Pester v4 and previous, but not v5. I’m hoping to update this post in the future to show how to use this same technique with Pester v5.]]></summary></entry><entry><title type="html">Order Outlook Deleted Items folder by date deleted</title><link href="https://blog.danskingdom.com/Order-Outlook-Deleted-Items-folder-by-date-deleted/" rel="alternate" type="text/html" title="Order Outlook Deleted Items folder by date deleted" /><published>2019-08-18T00:00:00+00:00</published><updated>2019-08-18T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Order-Outlook-Deleted-Items-folder-by-date-deleted</id><content type="html" xml:base="https://blog.danskingdom.com/Order-Outlook-Deleted-Items-folder-by-date-deleted/"><![CDATA[<p>If you’ve ever deleted an email by accident and had to hunt it down in your Outlook <code class="language-plaintext highlighter-rouge">Deleted Items</code> folder to recover it, this tip is for you! You can <a href="https://www.extendoffice.com/documents/outlook/1740-outlook-sort-deleted-email-items-by-deleted-date.html#a1">have Outlook sort your <code class="language-plaintext highlighter-rouge">Deleted Items</code> folder by date modified</a> so that the items show up in the order of the date they were deleted, rather than the date they were received, which is the default.</p>

<p>Too many times I’ve accidentally deleted the wrong email from my inbox because a new email came in and pushed all the emails in the inbox list down right when I was about to click the trash can icon on an email. By changing the sorting, that email will always be at the top of the <code class="language-plaintext highlighter-rouge">Deleted Items</code> folder, making it easy to recover.</p>

<blockquote>
  <p>Pro tip: You can also hit <kbd>Ctrl</kbd> + <kbd>Z</kbd> to undo the deletion.</p>
</blockquote>

<p>Also, sometimes I have emails sit in my inbox or other folders for days, weeks, or months, and I purposely delete them. Then a few hours or days later I realize I need them back. It’s much easier to recall the approximate date and time that I deleted the email than it is to remember what day I received the email on, making it easier to find in my <code class="language-plaintext highlighter-rouge">Deleted Items</code>. I know there’s also the search functionality, but sometimes you can’t recall text that was unique to the email to narrow the search results down enough.</p>

<p>I prefer my <code class="language-plaintext highlighter-rouge">Deleted Items</code> sorted this way, and perhaps you will too.</p>

<p>Happy <strike>coding</strike> emailing!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Email" /><category term="Outlook" /><category term="Email" /><category term="Outlook" /><summary type="html"><![CDATA[If you’ve ever deleted an email by accident and had to hunt it down in your Outlook Deleted Items folder to recover it, this tip is for you! You can have Outlook sort your Deleted Items folder by date modified so that the items show up in the order of the date they were deleted, rather than the date they were received, which is the default.]]></summary></entry><entry><title type="html">Migrating my blog from WordPress to Jekyll and GitHub Pages</title><link href="https://blog.danskingdom.com/Migrating-my-blog-from-WordPress-to-Jekyll-and-GitHub-Pages/" rel="alternate" type="text/html" title="Migrating my blog from WordPress to Jekyll and GitHub Pages" /><published>2019-08-17T00:00:00+00:00</published><updated>2019-08-17T00:00:00+00:00</updated><id>https://blog.danskingdom.com/Migrating-my-blog-from-WordPress-to-Jekyll-and-GitHub-Pages</id><content type="html" xml:base="https://blog.danskingdom.com/Migrating-my-blog-from-WordPress-to-Jekyll-and-GitHub-Pages/"><![CDATA[<p>You may have noticed my blog has a very different look now! It used to look like this.</p>

<p><img src="/assets/Posts/2019-08-17-Migrating-my-blog-from-WordPress-to-Jekyll-and-GitHub-Pages/OldWordPressBlogScreenshot.png" alt="Old WordPress Blog Screenshot" /></p>

<p>I decided to migrate my blog from WordPress to Jekyll, and this blog post highlights my reasons why and some of my experiences when doing so.</p>

<h2 id="what-is-jekyll">What is Jekyll</h2>

<p>You’ve probably heard of WordPress, but maybe not Jekyll. They are both platforms for hosting blog content. The big difference, in my eyes, is that WordPress stores all of it’s data in a MySQL database, while Jekyll’s data is all stored in text files. This means your entire blog contents can be stored in source control, such as Git.</p>

<p>Jekyll is used to compile the file contents and output all of the files for your static website, typically by running a command like <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code>. Jekyll typically supports websites with static content, making it ideal for blogs. Jekyll leverages the <a href="https://shopify.github.io/liquid/">Liquid</a> programming language for flow control (if statements, loops, etc.) and <a href="https://jekyllrb.com/docs/front-matter/">FrontMatter</a> for variables. It also allows you to write your website content in <a href="https://www.markdownguide.org/getting-started">Markdown</a>, as well as HTML.</p>

<p>Visit <a href="https://jekyllrb.com/">the official Jekyll site</a> to learn more and see how to get started with it.</p>

<h2 id="why-i-migrated-from-wordpress-to-jekyll">Why I migrated from WordPress to Jekyll</h2>

<p>You may have noticed that I haven’t blogged in quite a while; almost 2 years! While I was busy with work and family, another contributor was simply that the technology didn’t make it <em>easy</em> to post new content.</p>

<p>Reasons why I wanted to convert my blog away from WordPress:</p>

<ul>
  <li><strong>The Editor</strong> - I wasn’t a fan of the WordPress online editor, so I used <a href="http://openlivewriter.org">Open Live Writer</a>. It provided a nicer experience, but required some plugins for code snippets and images to work properly. Also, updating posts would often result in duplicate images and attachments being uploaded. It just wasn’t a great experience, and it required a bit of work to get everything setup correctly again after reinstalling Windows. Also, when editing posts later in WordPress, I’d have to wade through generated HTML code as well instead of focusing just on my content.</li>
  <li><strong>Special Characters</strong> - Special XML characters common in code (such as <code class="language-plaintext highlighter-rouge">&lt;</code>, <code class="language-plaintext highlighter-rouge">&gt;</code>, and <code class="language-plaintext highlighter-rouge">"</code>) would get converted to their escape sequence when posting through Open Live Writer, and I’d always have to go into the online editor and modify my code snippets to transform things like <code class="language-plaintext highlighter-rouge">&amp;lt;</code> back into their proper character.</li>
  <li><strong>Markdown</strong> - I wanted to write my posts in Markdown so that I could focus on my content, and less on it’s presentation. It also meant I wouldn’t need lean on a special editor or to mess with HTML. I tried a few WordPress plugins, but ultimately they didn’t meet my expectations.</li>
  <li><strong>Update Nightmares</strong> - WordPress and the plugins constantly had new updates that needed to be applied. My main issue was often the updates would fail, leaving my blog offline until I manually fixed things up; I learned very quickly to backup all files and the database before doing any updates. The resolution often involved grabbing the new version’s updated source code and manually applying the updated files to my WordPress instance over FTP, rather than using the <code class="language-plaintext highlighter-rouge">Update</code> button in the WordPress GUI. I’m not sure if this is a common problem for other WordPress users, but it was for me for several years.</li>
  <li><strong>Cost</strong> - I was paying $150+/year for web hosting with GoDaddy. When renewal time came around, I decided to migrate my WordPress blog from GoDaddy to Azure. This was even more expensive at ~$60/month to host the website and MySQL database, but I get $70/month free Azure credits with my Visual Studio subscription. So even though Azure was more expensive, it was technically free for me; however, it meant I couldn’t use those Azure credits on other things.</li>
</ul>

<p>Reasons why I decided to use Jekyll:</p>

<ul>
  <li><strong>Source Control</strong> - As a software developer, using a git repo is familiar to me, and it means all changes are in source control. There’s no external database dependency to manage and maintain. It also means no longer having to take backups.</li>
  <li><strong>Markdown</strong> - It supports writing in your posts in <a href="https://www.markdownguide.org/getting-started">Markdown</a>. Enough said.</li>
  <li><strong>Everything Is Text</strong> - Updating and creating posts is easy and can be done from any text editor, even just with the GitHub online editor. There’s no magic going on behind the scenes (other than the filename convention and FrontMatter); everything is plain text.</li>
  <li><strong>Customization</strong> - There are tons of themes available. If you don’t like them, you can customize them, or create your own from scratch, assuming you know HTML, CSS, Javascript, Liquid, and FrontMatter (before this migration I hadn’t heard of <a href="https://shopify.github.io/liquid/">Liquid</a> or <a href="https://jekyllrb.com/docs/front-matter/">FrontMatter</a>).</li>
  <li><strong>Preview Changes Locally</strong> - Jekyll allows you to host the site on your local machine to preview any changes you’ve made, whether it’s just a new post that you’ve written, or large sweeping change like changing your theme. This way I can preview my changes and make sure everything is the way I want it before pushing it to the live site.</li>
  <li><strong>It’s Free</strong> - Jekyll itself is <a href="https://github.com/jekyll/jekyll">open source</a> and free.</li>
  <li><strong>Host Your Site Anywhere</strong> - Because Jekyll simply outputs the HTML files of your site, you can host your site wherever you like; there are no special requirements from your hosting provider (e.g. supporting MySQL).</li>
  <li><strong>Free Hosting On GitHub Pages!</strong> - <a href="https://help.github.com/en/articles/setting-up-your-github-pages-site-locally-with-jekyll">GitHub Pages supports Jekyll</a> and allows you to host your site completely for free! <a href="https://jekyllrb.com/docs/github-pages/">More info</a>.</li>
</ul>

<h3 id="why-didnt-i-go-to-some-other-free-hosted-service">Why didn’t I go to some other free hosted service</h3>

<p>There are a ton of <a href="https://www.techradar.com/news/the-best-free-blogging-sites">free blog hosting options</a> out there, even programming focused ones like <a href="https://dev.to">dev.to</a>, so why did I decide to go this route?</p>

<p>I originally started my blog with <a href="http://geekswithblogs.net">GeeksWithBlogs.net</a>. A couple years after starting my blog, they announced that they were shutting down (although they still seem to be around today, *head-scratch*). With the thought of losing all of the hard work I had put into my blog content (even if it wasn’t great), it was then that I decided I was going to move into a self-hosted blog alternative. One where even if my provider disappeared one day, I wouldn’t lose all of my hard work.</p>

<p>Luckily there was an existing process for exporting from GeeksWithBlogs to WordPress, and that’s what I went with. With WordPress I would be able to have database backups and manually put my files into source control. Back then (circa 2012), this was very appealing. I wouldn’t be relying on a 3rd party service anymore, and would truly own (and host) my content. I decided to self-host my WordPress site so that I wouldn’t be relying on some 3rd party provider that might go under again, and so that I had more options between themes and plugins to use, while not subjecting my readers invasive advertisements.</p>

<p>Fast-forward 7 years; being able to natively store my blog in source control with free hosting is even more appealing.</p>

<h2 id="how-i-migrated-from-wordpress-to-jekyll">How I migrated from WordPress to Jekyll</h2>

<p>If you’re starting a new blog or website from scratch, using Jekyll is pretty straightforward and you can get up and running in a few minutes. I had a lot of existing content in WordPress that I wanted to migrate though, so I used the <a href="https://github.com/benbalter/wordpress-to-jekyll-exporter">WordPress to Jekyll Exporter plugin</a>, which worked great. I did have a problem with it at first though, in that <a href="https://github.com/benbalter/wordpress-to-jekyll-exporter/issues/145">it kept generating a zero-byte zip file</a>. Fortunately I was able to just roll it back from v2.3.0 to 2.2.3 and that version worked properly.</p>

<h3 id="massaging-the-jekyll-data">Massaging the Jekyll data</h3>

<p>Once I had the data out WordPress and in text files in the Jekyll file structure, there were a few changes I needed to make before it would show correctly:</p>

<ul>
  <li><strong>Image / Attachment URLs</strong> - When I exported the site the images and attachments were exported as well, but the their links in the posts were still using an absolute URL path that pointed back to my WordPress blog URL, rather than pointing to the local image / attachment files. This meant having to go through all my posts and updating the image and attachment links to use relative paths.</li>
  <li><strong>FrontMatter Variables</strong> - I had to update the FrontMatter on each post to match what my theme expected. For example, each post had a <code class="language-plaintext highlighter-rouge">layout: post</code> value, but my theme expected <code class="language-plaintext highlighter-rouge">layout: single</code>.
    <ul>
      <li>(optional) Remove extra unnecessary FrontMatter variables from all of the posts. e.g. Remove the <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">jabber_published</code>, and <code class="language-plaintext highlighter-rouge">guid</code> variables, as they weren’t used by my theme. They weren’t hurting anything by being there, but I like to keep my files as clean as possible.</li>
      <li>(optional) I also removed the <code class="language-plaintext highlighter-rouge">author</code> and <code class="language-plaintext highlighter-rouge">layout</code> variables from all my posts and instead set up default values in the <code class="language-plaintext highlighter-rouge">_config.yml</code> file. This just helps prevent duplicate text in all my posts FrontMatter.</li>
    </ul>
  </li>
  <li><strong>Convert HTML To Markdown (optional)</strong> - I went through all of my posts to convert any HTML to Markdown. HTML displayed fine, I’m just a bit picky and try to stick to pure Markdown. It also ensures that my older posts will look the same as my newer ones, as some of the HTML had colors and other styles hard-coded in it that would override whatever theme I chose.</li>
</ul>

<h2 id="finding-a-jekyll-theme-to-use">Finding a Jekyll theme to use</h2>

<p>There are <a href="https://rubygems.org/search?utf8=%E2%9C%93&amp;query=jekyll-theme">a lot of great Jekyll themes to choose from</a>. Some things to be aware of when choosing a theme though is that some actually provide different features, such as:</p>

<ul>
  <li>Google analytics</li>
  <li>Advertisements</li>
  <li>Support for comments</li>
  <li>Metadata on your posts (estimated read time, created and updated dates, etc.)</li>
</ul>

<p>If the theme you choose doesn’t support a specific feature, you can always add it in yourself, but it does require some development effort and know-how. It’s great if you can find one that not only looks great, but that also natively supports everything you want.</p>

<h3 id="different-ways-of-leveraging-a-jekyll-theme">Different ways of leveraging a Jekyll theme</h3>

<p>This was probably one of the most confusing parts of the migration for me. There are 3 different ways you can use a theme in Jekyll, and not all themes support all 3 ways. The 3 options you have are:</p>

<ol>
  <li><strong>Fork The Theme Repository</strong> - Basically clone all of the source code, and then go ahead and add your posts to it and customize it however you like. This is perhaps the most straightforward approach, but it doesn’t allow you to automatically take new updates to the theme; you would have to manually merge changes into your forked repository.</li>
  <li><strong>Ruby Gems</strong> - <a href="https://jekyllrb.com/docs/themes/">The official docs</a> describe this better than I ever could, but essentially you have a <code class="language-plaintext highlighter-rouge">Gemfile</code> that lists the dependencies of your site, including your theme. You run a command like <code class="language-plaintext highlighter-rouge">bundle update</code> to update your gem versions and pull the latest version of the gem’s files into your project. This is how you update your site to a newer version of the theme. In the .Net world, this is similar to a NuGet package. You don’t actually see the theme files in your project like when forking a theme repository though, so it helps keep your repo nice and clean with only the files you care about. Most themes support this method.</li>
  <li><strong>GitHub Remote Theme Repository</strong> - I believe this option is only available for themes hosted in GitHub (which most are), and not all themes support it. This method allows you to always be on the latest version of the theme without having to run any addition commands (e.g. <code class="language-plaintext highlighter-rouge">bundle update</code>). Rather than including the theme in your <code class="language-plaintext highlighter-rouge">Gemfile</code>, you instead include a line in your <code class="language-plaintext highlighter-rouge">_config.yml</code> that references the remote theme repo. e.g. <code class="language-plaintext highlighter-rouge">remote_theme: mmistakes/minimal-mistakes</code> to always use the latest version of the theme, or <code class="language-plaintext highlighter-rouge">mmistakes/minimal-mistakes@26c1989</code> to use a specific branch/commit/tag of the theme. This allows your site to always be on the latest version of the theme without any intervention, or use the <code class="language-plaintext highlighter-rouge">@branch/commit/tag</code> syntax to stay on a specific version until you decide to update. As with Ruby Gems, this option does not store the theme files in your repo, keeping your repo nice and clean. GitHub Pages has several built-in themes that <a href="https://help.github.com/en/articles/adding-a-jekyll-theme-to-your-github-pages-site">you can use for your Jekyll site</a> as well if you like. <a href="https://github.com/benbalter/jekyll-remote-theme">More info</a>.</li>
</ol>

<p>I ended up using the <a href="https://mmistakes.github.io/minimal-mistakes/">minimal mistakes</a> theme and the remote theme repository strategy.</p>

<h3 id="troubleshooting-jekyll-theme-issues">Troubleshooting Jekyll theme issues</h3>

<p>I first started out using the Ruby Gems approach, but ran into issues where the site wouldn’t display my posts; it seemed to work with some themes (ones I didn’t want to actually use), but not others. I didn’t understand why at the time, but it was due to my lack of understanding of how <code class="language-plaintext highlighter-rouge">Liquid</code> and <code class="language-plaintext highlighter-rouge">FrontMatter</code> worked with the themes. Not all themes looks for the same variables; some expect your posts to have <code class="language-plaintext highlighter-rouge">layout: post</code> defined on them, others want <code class="language-plaintext highlighter-rouge">layout: posts</code>, or <code class="language-plaintext highlighter-rouge">layout: single</code>, or something else. If the posts don’t have the theme’s expected FrontMatter variables defined on them, they might not be recognized as themes, causing them to not be displayed, or to be displayed, but not look how you expect them to.</p>

<p>In addition to specific FrontMatter variables, different themes often expect to find different variables defined in your <code class="language-plaintext highlighter-rouge">_config.yml</code> file as well. While there are some standard variables that most themes expect to find, such as <code class="language-plaintext highlighter-rouge">name</code> and <code class="language-plaintext highlighter-rouge">description</code>, theme’s may expect other variables as well depending on what features they offer. For example, the <a href="https://mmistakes.github.io/minimal-mistakes/">minimal mistakes theme</a> expected to find a <code class="language-plaintext highlighter-rouge">words_per_minute</code> variable so that it can display the estimated reading time of a post.</p>

<p>With Jekyll, <code class="language-plaintext highlighter-rouge">Liquid</code> accesses variables defined in the <code class="language-plaintext highlighter-rouge">_config.yml</code> by using the <code class="language-plaintext highlighter-rouge">site</code> keyword. For example, in my theme’s code it accesses the <code class="language-plaintext highlighter-rouge">words_per_minute</code> variable by using <code class="language-plaintext highlighter-rouge">site.words_per_minute</code>. Variables defined in a posts FrontMatter are accessed using the <code class="language-plaintext highlighter-rouge">post</code> keyword, such as <code class="language-plaintext highlighter-rouge">post.date</code>, where <code class="language-plaintext highlighter-rouge">date</code> would be a FrontMatter variable defined at the top of my posts.</p>

<p>Also, some themes only enable certain features when Jekyll is running in production mode. For example, <a href="https://mmistakes.github.io/minimal-mistakes/">minimal mistakes</a> only displays advertisements and the comment posting form when running in production mode. Running Jekyll in production mode involves setting the <code class="language-plaintext highlighter-rouge">JEKYLL_ENV</code> variable to <code class="language-plaintext highlighter-rouge">production</code>. You can set this variable when starting your Jekyll site by using <code class="language-plaintext highlighter-rouge">JEKYLL_ENV=production bundle exec jekyll serve</code>. For some reason, this wouldn’t work for me when running this command in PowerShell, and I instead had to use Bash (I’m running Windows btw). If using GitHub Pages to host your site, it will have the variable set to <code class="language-plaintext highlighter-rouge">production</code> by default.</p>

<p>So if your site is not displaying how you expect it to, read the theme’s documentation (if it has any), or dig right into its code to see what variables it expects to be defined at the <code class="language-plaintext highlighter-rouge">site</code> and <code class="language-plaintext highlighter-rouge">post</code> level.</p>

<p>Lastly, I’ve found that sometimes the best or quickest way to troubleshoot issues is to find somebody elses website that’s using the same theme or integrations as you and take a look at their code. Feel free to check out <a href="https://github.com/deadlydog/deadlydog.github.io">the code used to host this blog</a>.</p>

<h2 id="running-the-jekyll-site-on-github-pages">Running the Jekyll site on GitHub Pages</h2>

<p>Getting your site up and running on GitHub Pages is actually super easy. GitHub has <a href="https://help.github.com/en/articles/using-jekyll-as-a-static-site-generator-with-github-pages">some help docs</a> that walk you through it. The main points are:</p>

<ul>
  <li>In your repository’s <code class="language-plaintext highlighter-rouge">Settings</code> page, enable <code class="language-plaintext highlighter-rouge">GitHub Pages</code>.</li>
  <li>If your Jekyll site is on your User or Organization page (e.g. username.github.io), then GitHub Pages will build your site from the <code class="language-plaintext highlighter-rouge">master</code> branch.</li>
  <li>If your Jekyll site is on a Project page, it will build your site from the <code class="language-plaintext highlighter-rouge">gh-pages</code> branch.</li>
  <li>The <code class="language-plaintext highlighter-rouge">_config.yml</code> file and other Jekyll files must be at the root of the branch, or optionally <a href="https://help.github.com/en/articles/configuring-a-publishing-source-for-github-pages">in a <code class="language-plaintext highlighter-rouge">docs</code> folder at the root of the branch</a>.</li>
</ul>

<p>Pretty much though, if you’re able to build and serve your Jekyll site on your local computer, just push the files to your GitHub repo and it will compile and host your site for you. It may take a couple minutes for any changes to show on the GitHub Pages after you’ve pushed them to the GitHub repo, as it needs to compile your Jekyll site to generate the GitHub Pages files.</p>

<h2 id="using-staticman-to-get-comments-in-jekyll">Using Staticman to get comments in Jekyll</h2>

<p>With Jekyll producing a static website and not having a database, it doesn’t natively lend itself to comment submissions. Luckily, there are several options for adding comments to your Jekyll site, with one of the most popular being <a href="https://disqus.com">Disqus</a>. I decided to go with <a href="https://staticman.net/">Staticman</a> for my blog because, again, I didn’t want to be reliant on a 3rd party; I want to own all of the content and not worry about a 3rd party going out of business, changing their pricing model, or figuring out how to export my comments out of their system at a later point in time.</p>

<p>With <a href="https://staticman.net/">Staticman</a>, you <a href="https://mademistakes.com/articles/jekyll-static-comments/">add a form</a> <a href="https://mademistakes.com/articles/improving-jekyll-static-comments/">to your website</a> (if your theme doesn’t provide a built-in one). When a comment is submitted, it sends the information to the Staticman API, and then Staticman will open up a pull request against your GitHub (or GitLab) repository with the information. Once the pull request is completed, the comment will now be inside of your Git repository, causing GitHub Pages to rebuild your Jekyll site and display the new comment.</p>

<p>The idea of how Staticman works is simple enough, and actually pretty clever. Unfortunately when I went to integrate Staticman into my Jekyll site (summer 2019), there were issues with Staticman. The main issue was that the official documentation says to use a free publicly hosted instance of the Staticman API, and gives the URL for it. The problem is that it’s using a free hosting plan and is limited to a certain number of requests per day. As more and more people adopted it, it began reaching it’s quota very often, resulting in frequent <code class="language-plaintext highlighter-rouge">409 Too many requests</code> errors when people tried to submit comments.</p>

<p>This led me down the path of hosting my own private instance of Staticman. Luckily, a few other people had already taken this route as well and <a href="https://www.datascienceblog.net/post/other/staticman_comments/">blogged</a> <a href="https://vincenttam.gitlab.io/post/2018-09-16-staticman-powered-gitlab-pages/2/">some</a> <a href="https://bloggerbust.ca/series/staticman-with-github-and-zeit-now/">nice</a> <a href="https://github.com/eduardoboucas/staticman/issues/293">tutorials</a> for various hosting providers. The unfortunate bit was the official Staticman docs had not been updated to include any of this information, so finding it involved hunting through numerous GitHub issues on the Staticman repository. Also, many of the code changes required for these hosting options had not been merged into the Staticman <code class="language-plaintext highlighter-rouge">dev</code> or <code class="language-plaintext highlighter-rouge">master</code> branches yet, so it meant using an “unofficial” Staticman branch.</p>

<p>For myself, I ended up using Heroku to host my Staticman instance. It’s completely free, and since I’m the only one hitting the API, I shouldn’t run over my API request limit. <a href="https://github.com/eduardoboucas/staticman/issues/294">This GitHub issue</a> provides more information around exactly what I did and the issues I encountered and their resolutions. My main issues had to do around using ReCaptcha on comment submissions, but I got it all figured out in the end.</p>

<h2 id="bonus-section-my-editor-preference">Bonus section: My editor preference</h2>

<p>While I can technically use any text editor to create blog posts, my favourite editor is VS Code, for a number of reasons:</p>

<ul>
  <li>It has native Git support, and pushing my changes up to GitHub is a breeze.</li>
  <li>It has a built-in terminal, making it easy to build and serve the Jekyll site locally to preview my changes.</li>
  <li>It has a built-in Markdown previewer, so if I don’t want to actually spin up my site locally to preview the changes, I can use the built-in editor to quickly verify that my Markdown syntax is all correct and looks the way I expect.</li>
  <li>It has some amazing extensions that make writing posts in markdown a great experience:
    <ul>
      <li><a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one">Markdown All in One</a></li>
      <li><a href="https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint">markdownlint</a></li>
      <li><a href="https://marketplace.visualstudio.com/items?itemName=ionutvmi.path-autocomplete">Path Autocomplete</a></li>
      <li><a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker">Code Spell Checker</a></li>
    </ul>
  </li>
  <li>It also has many extensions for doing web development, which is handy if you want to customize your Jekyll site:
    <ul>
      <li><a href="https://marketplace.visualstudio.com/items?itemName=abusaidm.html-snippets">HTML Snippets</a></li>
      <li><a href="https://marketplace.visualstudio.com/items?itemName=ecmel.vscode-html-css">HTML CSS Support</a></li>
      <li><a href="https://marketplace.visualstudio.com/items?itemName=sissel.shopify-liquid">Liquid</a></li>
    </ul>
  </li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Hopefully this has given you some insight or tips into what you can do with your own blog. I chose Jekyll because it allows me to host the website wherever I like, and I completely own my content and don’t have to worry about my hosting provider going out of business. I also chose it because GitHub Pages natively supports it, and can host my blog completely for free.</p>

<p>The hardest part was getting my existing content changed into a format that my chosen theme expected. I think starting fresh from scratch would be much easier, regardless of what theme you choose to go with. I’m not certain that I would recommend Jekyll for somebody non-technical (like my mom), but for software developers who like to write in Markdown it’s definitely a top contender.</p>

<p>Happy <strike>coding</strike> blogging :)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Jekyll" /><category term="GitHub Pages" /><category term="WordPress" /><category term="Staticman" /><category term="Jekyll" /><category term="GitHub Pages" /><category term="WordPress" /><category term="Staticman" /><category term="Migrate" /><summary type="html"><![CDATA[You may have noticed my blog has a very different look now! It used to look like this.]]></summary></entry><entry><title type="html">PowerShell Log Levels Included In TFS 2017 and VSTS Build and Release Logs</title><link href="https://blog.danskingdom.com/powershell-log-levels-included-in-tfs-2017-and-vsts-build-and-release-logs/" rel="alternate" type="text/html" title="PowerShell Log Levels Included In TFS 2017 and VSTS Build and Release Logs" /><published>2017-10-26T07:07:25+00:00</published><updated>2017-10-26T07:07:25+00:00</updated><id>https://blog.danskingdom.com/powershell-log-levels-included-in-tfs-2017-and-vsts-build-and-release-logs</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-log-levels-included-in-tfs-2017-and-vsts-build-and-release-logs/"><![CDATA[<p>We use quite a few custom PowerShell scripts in some of our builds and releases. This led me to ask the question, which PowerShell log levels actually get written to the TFS Build and Release logs? So I did a quick test on both our on-premise TFS 2017 Update 2 installation and my personal VSTS account, and they yielded the same results.</p>

<h2 id="the-powershell-script-used-to-test">The PowerShell Script Used To Test</h2>

<p>I created a blank build definition and release definition, and the only thing I added to them was a PowerShell task with the following inline script:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Host"</span><span class="w">
</span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"Output"</span><span class="w">
</span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Debug"</span><span class="w">
</span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Debug Forced"</span><span class="w"> </span><span class="nt">-Debug</span><span class="w">
</span><span class="n">Write-Information</span><span class="w"> </span><span class="s2">"Information"</span><span class="w">
</span><span class="nv">$InformationPreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Continue'</span><span class="w">
</span><span class="n">Write-Information</span><span class="w"> </span><span class="s2">"Information Forced"</span><span class="w">
</span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Verbose"</span><span class="w">
</span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Verbose Forced"</span><span class="w"> </span><span class="nt">-Verbose</span><span class="w">
</span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Warning"</span><span class="w">
</span><span class="n">Write-Error</span><span class="w"> </span><span class="s2">"Error"</span><span class="w">
</span><span class="kr">throw</span><span class="w"> </span><span class="s2">"Throw"</span><span class="w">
</span></code></pre></div></div>

<h2 id="the-results">The Results</h2>

<p>Both the build and the release logs yielded the same results. The real-time output shown in the Console resulted in:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host
Output
DEBUG: Debug Forced
Write-Debug : Windows PowerShell is in NonInteractive mode. Read and Prompt functionality is not available.
Information Forced
VERBOSE: Verbose Forced
WARNING: Warning
C:\Builds\_work\_temp\c0558237-7d53-43ca-97bf-90ed03b8247f.ps1 : Error
</code></pre></div></div>

<p><img src="/assets/Posts/2017/10/PowerShellConsoleOutput.png" alt="PowerShell console output" /></p>

<p>The more detailed information written to the log files was:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.5118196</span><span class="n">Z</span><span class="w"> </span><span class="nx">Host</span><span class="w">
</span><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.5138198</span><span class="n">Z</span><span class="w"> </span><span class="nx">Output</span><span class="w">
</span><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.5228183</span><span class="n">Z</span><span class="w"> </span><span class="nx">DEBUG:</span><span class="w"> </span><span class="nx">Debug</span><span class="w"> </span><span class="nx">Forced</span><span class="w">
</span><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.6678192</span><span class="n">Z</span><span class="w"> </span><span class="c">##[error]Write-Debug : Windows PowerShell is in NonInteractive mode. Read and Prompt functionality is not available.</span><span class="w">
</span><span class="n">At</span><span class="w"> </span><span class="nx">D:\a\_temp\3b089729-e7c5-484d-aa58-256b33f12e01.ps1:4</span><span class="w"> </span><span class="nx">char:1</span><span class="w">
</span><span class="o">+</span><span class="w"> </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Debug Forced"</span><span class="w"> </span><span class="nt">-Debug</span><span class="w">
</span><span class="o">+</span><span class="w"> </span><span class="n">~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</span><span class="w">
    </span><span class="o">+</span><span class="w"> </span><span class="nx">CategoryInfo</span><span class="w">          </span><span class="p">:</span><span class="w"> </span><span class="nx">InvalidOperation:</span><span class="w"> </span><span class="p">(:)</span><span class="w"> </span><span class="p">[</span><span class="n">Write</span><span class="nt">-Debug</span><span class="p">],</span><span class="w"> </span><span class="n">PSInvalidOperationException</span><span class="w">
    </span><span class="o">+</span><span class="w"> </span><span class="nx">FullyQualifiedErrorId</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nx">InvalidOperation</span><span class="p">,</span><span class="nx">Microsoft.PowerShell.Commands.WriteDebugCommand</span><span class="w">


</span><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.6698195</span><span class="n">Z</span><span class="w"> </span><span class="nx">Information</span><span class="w"> </span><span class="nx">Forced</span><span class="w">
</span><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.6698195</span><span class="n">Z</span><span class="w"> </span><span class="nx">VERBOSE:</span><span class="w"> </span><span class="nx">Verbose</span><span class="w"> </span><span class="nx">Forced</span><span class="w">
</span><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.6698195</span><span class="n">Z</span><span class="w"> </span><span class="nx">WARNING:</span><span class="w"> </span><span class="nx">Warning</span><span class="w">
</span><span class="mi">2017</span><span class="nt">-10-26T06</span><span class="p">:</span><span class="mi">31</span><span class="p">:</span><span class="mf">09.7258181</span><span class="n">Z</span><span class="w"> </span><span class="c">##[error]D:\a\_temp\3b089729-e7c5-484d-aa58-256b33f12e01.ps1 : Error</span><span class="w">
</span><span class="n">At</span><span class="w"> </span><span class="nx">line:1</span><span class="w"> </span><span class="nx">char:1</span><span class="w">
</span><span class="o">+</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="s1">'d:\a\_temp\3b089729-e7c5-484d-aa58-256b33f12e01.ps1'</span><span class="w">
</span><span class="o">+</span><span class="w"> </span><span class="n">~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</span><span class="w">
    </span><span class="o">+</span><span class="w"> </span><span class="nx">CategoryInfo</span><span class="w">          </span><span class="p">:</span><span class="w"> </span><span class="nx">NotSpecified:</span><span class="w"> </span><span class="p">(:)</span><span class="w"> </span><span class="p">[</span><span class="n">Write</span><span class="nt">-Error</span><span class="p">],</span><span class="w"> </span><span class="n">WriteErrorException</span><span class="w">
    </span><span class="o">+</span><span class="w"> </span><span class="nx">FullyQualifiedErrorId</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nx">Microsoft.PowerShell.Commands.WriteErrorException</span><span class="p">,</span><span class="nx">3b089729-e7c5-484d-aa58-256b33f12e01.p</span><span class="w">
   </span><span class="n">s1</span><span class="w">

</span><span class="kr">Throw</span><span class="w">
</span><span class="n">At</span><span class="w"> </span><span class="nx">D:\a\_temp\3b089729-e7c5-484d-aa58-256b33f12e01.ps1:12</span><span class="w"> </span><span class="nx">char:1</span><span class="w">
</span><span class="o">+</span><span class="w"> </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"Throw"</span><span class="w">
</span><span class="o">+</span><span class="w"> </span><span class="n">~~~~~~~~~~~~~</span><span class="w">
    </span><span class="o">+</span><span class="w"> </span><span class="nx">CategoryInfo</span><span class="w">          </span><span class="p">:</span><span class="w"> </span><span class="nx">OperationStopped:</span><span class="w"> </span><span class="p">(</span><span class="kr">Throw</span><span class="p">:</span><span class="n">String</span><span class="p">)</span><span class="w"> </span><span class="p">[],</span><span class="w"> </span><span class="n">RuntimeException</span><span class="w">
    </span><span class="o">+</span><span class="w"> </span><span class="nx">FullyQualifiedErrorId</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nx">Throw</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2017/10/PowerShellLogOutput.png" alt="PowerShell log output" /></p>

<p>One thing to note is that while the <strong>throw</strong> statements are not shown in the real-time console output, they do show up in the detailed logs that are available once the task step completes.</p>

<p>It’s worth pointing out that even when toggling the <strong>system.debug</strong> variable to true, regular Debug statements were not written; only the Forced Debug statement was. Also, notice that even though the forced Debug statement was written to the log, it resulted in an error since the script is running in non-interactive mode, so you should probably avoid forcing Debug statements.</p>

<h2 id="summary">Summary</h2>

<p>So there you have it. By default only the following statements are written to the log files:</p>

<ul>
  <li>Write-Host</li>
  <li>Write-Output</li>
  <li>Write-Warning</li>
  <li>Write-Error</li>
</ul>

<p>and if you force them, you can also have the following written to the log files as well:</p>

<ul>
  <li>Write-Debug</li>
  <li>Write-Information</li>
  <li>Write-Verbose</li>
</ul>

<p>And lastly, <strong>throw</strong> statements do not show up in the real-time console output, but do show up in the detailed log information.</p>

<p>I hope you find this information useful. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="Deploy" /><category term="PowerShell" /><category term="TFS" /><category term="VSTS" /><category term="Build" /><category term="Deploy" /><category term="Logs" /><category term="PowerShell" /><category term="Team Foundation Server" /><category term="TFS" /><category term="Visual Studio Team Services" /><category term="VSTS" /><summary type="html"><![CDATA[We use quite a few custom PowerShell scripts in some of our builds and releases. This led me to ask the question, which PowerShell log levels actually get written to the TFS Build and Release logs? So I did a quick test on both our on-premise TFS 2017 Update 2 installation and my personal VSTS account, and they yielded the same results.]]></summary></entry><entry><title type="html">Strongly sign your Visual Studio extension’s 3rd party assemblies to avoid assembly-loading errors at runtime</title><link href="https://blog.danskingdom.com/strongly-sign-your-visual-studio-extensions-3rd-party-assemblies-to-avoid-assembly-loading-errors-at-runtime/" rel="alternate" type="text/html" title="Strongly sign your Visual Studio extension’s 3rd party assemblies to avoid assembly-loading errors at runtime" /><published>2017-06-12T08:21:50+00:00</published><updated>2017-06-12T08:21:50+00:00</updated><id>https://blog.danskingdom.com/strongly-sign-your-visual-studio-extensions-3rd-party-assemblies-to-avoid-assembly-loading-errors-at-runtime</id><content type="html" xml:base="https://blog.danskingdom.com/strongly-sign-your-visual-studio-extensions-3rd-party-assemblies-to-avoid-assembly-loading-errors-at-runtime/"><![CDATA[<p>When trying to create a Visual Studio 2017 version of my <a href="https://github.com/deadlydog/VS.DiffAllFiles">Diff All Files Visual Studio extension</a>, I was encountering a runtime error indicating that a module could not be loaded in one of the 3rd party libraries I was referencing (LibGit2Sharp):</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.TypeInitializationException occurred
  HResult=0x80131534
  Message=The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an exception.
  Source=LibGit2Sharp
  StackTrace:
   at LibGit2Sharp.Core.NativeMethods.git_repository_discover(GitBuf buf, FilePath start_path, Boolean across_fs, FilePath ceiling_dirs)
   at LibGit2Sharp.Core.Proxy.&lt;&gt;c__DisplayClass3e.&lt;git_repository_discover&gt;b__3d(GitBuf buf)
   at LibGit2Sharp.Core.Proxy.ConvertPath(Func`2 pathRetriever)
   at LibGit2Sharp.Core.Proxy.git_repository_discover(FilePath start_path)
   at LibGit2Sharp.Repository.Discover(String startingPath)
   at VS_DiffAllFiles.StructuresAndEnums.GitHelper.GetGitRepositoryPath(String path) in D:\dev\Git\VS.DiffAllFiles\VS.DiffAllFiles\GitHelper.cs:line 39

Inner Exception 1:
DllNotFoundException: Unable to load DLL 'git2-a5cf255': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
</code></pre></div></div>

<p>By using the <a href="https://docs.microsoft.com/en-us/dotnet/framework/tools/fuslogvw-exe-assembly-binding-log-viewer">Assembly Binding Log Viewer</a>, a.k.a. fuslogvw.exe, and being sure to run it “as administrator” so I could modify the settings, I was able to see this error at runtime when it tried to load my extension:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LOG: Binding succeeds. Returns assembly from C:\USERS\DAN.SCHROEDER\APPDATA\LOCAL\MICROSOFT\VISUALSTUDIO\15.0_B920D444EXP\EXTENSIONS\DANSKINGDOM\DIFF ALL FILES FOR VS2017\1.0\LibGit2Sharp.dll.
LOG: Assembly is loaded in LoadFrom load context.
WRN: Multiple versions of the same assembly were loaded into one context of an application domain:
WRN: Context: LoadFrom | Domain ID: 1 | Assembly Name: LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333
WRN: Context: LoadFrom | Domain ID: 1 | Assembly Name: LibGit2Sharp, Version=0.22.0.0, Culture=neutral, PublicKeyToken=7cbde695407f0333
WRN: This might lead to runtime failures.
WRN: It is recommended that you remove the dependency on multiple versions, and change the app.config file to point to the required version of the assembly only.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information.
</code></pre></div></div>

<p>My extension was using the LibGit2Sharp v0.23.1.0 version, but here it said there was already v0.22.0.0 of that assembly loaded in the app domain. Looking at some of the other logs in the Assembly Binding Log Viewer from when Visual Studio was running, I could see that the <a href="https://visualstudio.github.com/">GitHub Extension for Visual Studio</a> that I had installed was loading the 0.22.0.0 version into the app domain before my extension had been initiated. So the problem was that Visual Studio had already loaded an older version of the assembly that my extension depended on, so my extension was using that older version instead of the intended version. The solution to this problem was for me to give a new strong name to the LibGit2Sharp.dll that I included with my extension, so that both assemblies could be loaded into the app domain without conflicting with one another. To do this, I ran the following batch script against the LibGit2Sharp.dll file in the packages directory (replacing $(SolutionDirectory) with the path to my solution):</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:: Strongly sign the LibGit2Sharp.dll, as VS Extensions want strongly signed assemblies and we want to avoid runtime version conflicts.
:: http://www.codeproject.com/Tips/341645/Referenced-assembly-does-not-have-a-strong-name
<span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span>SolutionDir<span class="si">)</span><span class="s2">packages</span><span class="se">\L</span><span class="s2">ibGit2Sharp.0.23.1</span><span class="se">\l</span><span class="s2">ib</span><span class="se">\n</span><span class="s2">et40</span><span class="se">\"</span><span class="s2">
"</span>C:<span class="se">\P</span>rogram Files <span class="o">(</span>x86<span class="o">)</span><span class="se">\M</span>icrosoft SDKs<span class="se">\W</span>indows<span class="se">\v</span>10.0A<span class="se">\b</span><span class="k">in</span><span class="se">\N</span>ETFX 4.6.2 Tools<span class="se">\i</span>ldasm.exe<span class="s2">" /all /out=LibGit2Sharp.il LibGit2Sharp.dll
"</span>C:<span class="se">\P</span>rogram Files <span class="o">(</span>x86<span class="o">)</span><span class="se">\M</span>icrosoft SDKs<span class="se">\W</span>indows<span class="se">\v</span>10.0A<span class="se">\b</span><span class="k">in</span><span class="se">\N</span>ETFX 4.6.2 Tools<span class="se">\s</span>n.exe<span class="s2">" -k MyLibGit2SharpKey.snk
"</span>C:<span class="se">\W</span>indows<span class="se">\M</span>icrosoft.NET<span class="se">\F</span>ramework64<span class="se">\v</span>4.0.30319<span class="se">\i</span>lasm.exe<span class="s2">" /dll /key=MyLibGit2SharpKey.snk LibGit2Sharp.il
</span></code></pre></div></div>

<p>This overwrote the LibGit2Sharp.dll file in the packages directory with a new custom-signed version. One caveat with this is you need to make sure you have the Windows SDK installed (<a href="https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk">Win 10</a>, <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-8-1-sdk">Win 8.1</a>, or <a href="https://www.microsoft.com/en-ca/download/details.aspx?id=8279">Win 7</a>) and make sure all the paths to ildasm.exe and sn.exe are correct, as the version in the file path may be different depending on which version of Windows you are running. Once I had the new custom-signed .dll file, I added it to a new location source control and then removed any project references from the old assembly and added the new references to the custom-signed assembly. This process will need to be repeated any time I update to a new version of the assembly.</p>

<p>There may be other better solutions to this problem, but this one worked for me. Another possible solution is to <a href="https://www.codeproject.com/Articles/9364/Merging-NET-assemblies-using-ILMerge">use ILMerge</a> to <a href="https://stackoverflow.com/questions/9376/ilmerge-best-practices">combine the required .dll files into a single .dll file</a>; I haven’t tried this method myself however, so I cannot comment on if it is better/easier or not. One thing to mention about the ILMerge method however is it requires a post-build step, either to be added to a project in Visual Studio or to your build scripts. With the method I mentioned above, you only need to take additional steps when updating the version of the 3rd party assembly being used. If you know of another way around this problem, please share it in the comments.</p>

<p>I’m also going to give a shout out to the community in the <a href="https://gitter.im/Microsoft/extendvs">Gitter Microsoft/extendvs channel</a>, as they have been very helpful in helping me diagnose problems and come up with solutions while trying to port my extension to Visual Studio 2017.</p>

<p>For those interested, I documented <a href="https://github.com/deadlydog/VS.DiffAllFiles/blob/master/docs/internal/SupportingNewVisualStudioVersions.md">the process I took to update my extension to support VS 2017</a>, and <a href="https://github.com/deadlydog/VS.DiffAllFiles/blob/master/VS.DiffAllFiles/_LibGit2Sharp/ProcessForUpdatingLibGit2Sharp.txt">the process required to update LibGit2Sharp to a new version</a> in the future.</p>

<p>Hopefully you have found this post helpful. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="assembly" /><category term="dll" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><summary type="html"><![CDATA[When trying to create a Visual Studio 2017 version of my Diff All Files Visual Studio extension, I was encountering a runtime error indicating that a module could not be loaded in one of the 3rd party libraries I was referencing (LibGit2Sharp):]]></summary></entry><entry><title type="html">Continuously Deploy Your ClickOnce Application From Your Build Server</title><link href="https://blog.danskingdom.com/continuously-deploy-your-clickonce-application-from-your-build-server/" rel="alternate" type="text/html" title="Continuously Deploy Your ClickOnce Application From Your Build Server" /><published>2017-01-10T08:13:33+00:00</published><updated>2017-01-10T08:13:33+00:00</updated><id>https://blog.danskingdom.com/continuously-deploy-your-clickonce-application-from-your-build-server</id><content type="html" xml:base="https://blog.danskingdom.com/continuously-deploy-your-clickonce-application-from-your-build-server/"><![CDATA[<p>ClickOnce applications are a great and easy way to distribute your applications to many users, and have the advantage of offering automatic application updates out-of-the-box. Even though ClickOnce applications are super easy to deploy from Visual Studio (literally 3 clicks, just click Build –&gt; Publish –&gt; Finish), you may still want to have your build system publish the updates for various reasons, such as:</p>

<ol>
  <li>You don’t want you (or your team members) to have to remember to manually publish a new version all the time; even if it is very quick and easy.</li>
  <li>If you are signing your ClickOnce application (to make your app more secure and avoid annoying Windows security warnings during every update), your team members may not have the certificate installed, or you may not want them to know the certificate password.</li>
  <li>All of the other benefits of deploying automatically from a build server; you can be sure the project was built in Release mode, all unit tests have run and passed, you are only publishing a new version of the application from a specific branch (e.g. master), etc.</li>
</ol>

<p>In this post I’m going to show how to continuously deploy your ClickOnce application using Visual Studio Team Services (VSTS), but you can adopt what’s shown here to work on any build system, such as Team City or Jenkins.</p>

<h2 id="step-1--configure-and-publish-your-clickonce-application-manually">Step 1 – Configure and publish your ClickOnce application manually</h2>

<p>Before we can publish a new version of the application from the build server, we first have to build the project to create the artifacts to publish. And even before that, we have to make sure the project is setup properly with all of the required ClickOnce metadata. You will typically do this by going into your project Properties page and going to the Publish tab. There are several other websites and blog posts that discuss configuring a project for ClickOnce deployment, including the official MSDN documentation for <a href="https://msdn.microsoft.com/en-us/library/ff699224.aspx">configuring</a> and <a href="https://msdn.microsoft.com/en-us/library/748fh114.aspx">publishing</a> ClickOnce applications, so I won’t go into it any further here.</p>

<p>Basically you should have your project in a state where you can easily publish a new version manually, and it is configured the way that you want (includes the necessary files, has the destination to publish to specified, specifies if the application is only available online or not, etc.). Once you’ve published your project manually and confirmed that it’s configured the way you like, we can move onto the next step.</p>

<h2 id="step-2--setup-the-build-on-your-build-server">Step 2 – Setup the build on your build server</h2>

<p>On your build server you will want to configure a new vanilla build that builds your project/solution. There are a couple modifications you will need to make that are different from building a regular console app or class library project.</p>

<p>The first difference is that you will need to provide the “/target:Publish” argument to MSBuild when it builds your project. Here is what this looks like in VSTS:</p>

<p><img src="/assets/Posts/2017/01/Build-MSBuildPublishTarget.png" alt="Build - MsBuild publish target" /></p>

<p>This will cause MSBuild to build the required artifacts into an “app.publish” directory. e.g. bin\Debug\app.publish.</p>

<p>The next difference is that you will want to copy that “app.publish” directory to your build artifacts directory. To do this, you will need to add a Copy Files step into your build process that copies the “app.publish” directory from the ClickOnce project’s bin directory to where the build artifacts are expected to be. You will want to do this before the step that publishes your build artifacts. Here is what this looks like in VSTS:</p>

<p><img src="/assets/Posts/2017/01/Build-CopyFilesToArtifactsDirectory.png" alt="Build - Copy files to artifacts directory" /></p>

<p>So we copy the files into the build artifacts directory, and then the Publish Build Artifacts step at the end will copy those files to wherever you’ve specified; in my case it’s a network share.</p>

<p>If you like you can now run the build and see if it succeeds. If the build fails with an error relating to an expired certificate or pfx file, see <a href="https://blog.danskingdom.com/creating-a-pfx-certificate-and-applying-it-on-the-build-server-at-build-time/">my other blog post on importing the required certificate on the build server at build-time</a>, which involves adding one more “Import-PfxCertificate.ps1” build step before the MSBuild step.</p>

<p>For completeness sake, this is what my Publish Build Artifacts step looks like in VSTS, and you’ll also notice the “Import-PfxCertificate.ps1” step before the MSBuild step as well:</p>

<p><img src="/assets/Posts/2017/01/Build-PublishBuildArtifacts.png" alt="Build - Publish build artifacts" /></p>

<p>So we now have the ClickOnce artifacts being generated and stored in the appropriate directory. If you wanted, you could publish the build artifacts to the ClickOnce application’s final destination right now (instead of a file share as I’ve done here), but I’m going to follow best practices and separate the application “build” and “deployment” portions into their respective subsystems, as you may want separate control over when a build gets published, or maybe you don’t want to publish EVERY build; only some of them.</p>

<p>Hopefully at this point you are able to create a successful build, but we’re not done yet.</p>

<h2 id="step-3--publish-the-build-artifacts-to-the-clickonce-applications-destination">Step 3 – Publish the build artifacts to the ClickOnce application’s destination</h2>

<p>Now that we have the build artifacts safely stored, we can publish them to the ClickOnce application’s destination. With VSTS this is done by using the Release subsystem. So create a new Release Definition and setup an Environment for it. By default it adds a “Copy and Publish Build Artifacts” step to the release definition. When configuring and using this default step, I received the error:</p>

<blockquote>
  <p>[error]Copy and Publish Build Artifacts task is not supported within Release</p>
</blockquote>

<p>So instead I removed that default step and added a simple “Copy Files” step, and then configured it to grab the files from the file share that the build published the artifacts to, and set the destination to where the ClickOnce application was publishing to when I would do a manual publish from within Visual Studio. Here is what that step looks like in VSTS:</p>

<p><img src="/assets/Posts/2017/01/Release-PublishBuildArtifacts.png" alt="Release - Publish build artifacts" /></p>

<p>You should be able to run this release definition and see that it is able to post a new version of your ClickOnce application. Hooray! I setup my releases to automatically publish on every build, but you can configure yours however you like.</p>

<p>If you make changes and commit them, the build should create new artifacts, and the release should be able to publish the new version. However, if you launch your ClickOnce application, you may be surprised that it doesn’t actually update to the latest version that you just committed and published. This is because we need one additional build step to actually update the ClickOnce version so it can detect that a new version was published. If you launch another build and publish them, you’ll see that the files being published are overwriting the previous files, instead of getting published to a new directory as they are supposed to be.</p>

<p>You may be wondering why we need another build step to update the ClickOnce version. Part of the build artifacts that are created are a .application manifest file, and a directory that contains the applications files, both of which have the ClickOnce version in them. Can’t we just modify the directory name and .application manifest file to increment the old version number? This was my initial thought, but the .application manifest file contains a cryptography hash to ensure that nobody has tampered with it, meaning that it needs to contain the proper version number at the time that it is generated.</p>

<h2 id="step-4--one-more-build-step-to-update-the-clickonce-version">Step 4 – One more build step to update the ClickOnce version</h2>

<p>The ClickOnce version is defined in your project file (.csproj/.vbproj) (as the <code class="language-plaintext highlighter-rouge">&lt;ApplicationVersion&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;ApplicationRevision&gt;</code> xml elements), and is different from the assembly version that you would typically set to version your .dll/.exe files via the AssemblyInfo.cs file. Unless you manually updated the ClickOnce version in Visual Studio, it will still have its original version. When publishing the ClickOnce version in Visual Studio, Visual Studio automatically takes care of incrementing ClickOnce version’s Revision part. However, because we’re publishing from our build system now, we’ll need to come up with an alternative method. To help solve this problem, I have created <a href="https://github.com/deadlydog/Set-ProjectFilesClickOnceVersion">the Set-ProjectFilesClickOnceVersion GitHub repository</a>. This repository houses a single <a href="https://github.com/deadlydog/Set-ProjectFilesClickOnceVersion/releases">Set-ProjectFilesClickOnceVersion.ps1 PowerShell script</a> that we will want to download and add into our build process.</p>

<p>In order for the build system to access this Set-ProjectFilesClickOnceVersion.ps1 script you will need to check it into source control. Also, you need to make sure you add this build step before the MSBuild step. Here is what this step looks like in VSTS:</p>

<p><img src="/assets/Posts/2017/01/Build-SetProjectFilesClickOnceVersion.png" alt="Build - Set project files ClickOnce version" /></p>

<p>The Script Filename points to the location of the Set-ProjectFilesClickOnceVersion.ps1 script in my repository. Also note that I set the Working Folder to the directory that contains the .csproj project file. This is necessary since we are dealing with relative paths, not absolute ones.</p>

<p>For the Arguments I provide the name of the project file that corresponds to my ClickOnce application, as well as the VSTS Build ID. The Build ID is a globally unique ID that gets auto-incremented with every build, and most (all?) build systems have this. The script will translate this Build ID into the Build and Revision version number parts to use for the ClickOnce version, to ensure that they are always increasing in value. So you should never need to manually modify the ClickOnce version, and every build should generate a version number greater than the last, allowing the ClickOnce application to properly detect when a newer version has been published.</p>

<p>Maybe you update your application’s version number on every build, and want your ClickOnce version to match that version. If this is the case, then use the –Version parameter instead of –BuildSystemsBuildId. The other alternative is to use the –IncrementProjectFilesRevision switch to simply increment the ClickOnce Revision that is already stored in the project file. If you use this switch, you need to be sure to check the project file back into source control so that the Revision is properly incremented again on subsequent builds. I like to avoid my build system checking files into source control wherever possible, so I have opted for the BuildSystemsBuildId parameter here.</p>

<p>The last argument I have included is the UpdateMinimumRequiredVersionToCurrentVersion parameter, and it is optional. If provided, this parameter will change the ClickOnce application’s Minimum Required Version to be the same as the new version, forcing the application to always update to the latest version when it detects that an update is available. I typically use this setting for all of my ClickOnce applications. If you still plan on publishing your ClickOnce applications from Visual Studio, but want this functionality, simply install the <a href="https://www.nuget.org/packages/AutoUpdateProjectsMinimumRequiredClickOnceVersion">AutoUpdateProjectsMinimumRequiredClickOnceVersion NuGet Package</a>.</p>

<p>That’s it! Now if you launch another build it should create new artifacts with a larger ClickOnce version number, and once published your application should update to the new version.</p>

<h2 id="bonus--displaying-the-clickonce-version-in-your-application">Bonus – Displaying the ClickOnce version in your application</h2>

<p>The ClickOnce application version number may be different than your assembly version number, or perhaps you don’t update your product’s version number on every publish; it’s easy to forget. I typically like to use <a href="http://semver.org">semantic versioning</a> for my projects, which only involves the first 3 version parts. This leaves the last version part, the Revision number, available for me to set as I please. I will typically use the ClickOnce Revision in my application’s displayed version number. This makes it easy to tell which actual release a client has installed in the event that the product version was not updated between releases. The code to do it is fairly simple.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">Version</span> <span class="n">ApplicationVersion</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Version</span><span class="p">(</span><span class="s">"1.11.2"</span><span class="p">);</span>

<span class="k">private</span> <span class="k">void</span> <span class="nf">MainWindow_Loaded</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">RoutedEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// If this is a ClickOnce deployment, append the ClickOnce Revision to the version number (as it gets updated with each publish).</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ApplicationDeployment</span><span class="p">.</span><span class="n">IsNetworkDeployed</span><span class="p">)</span>
        <span class="n">ApplicationVersion</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Version</span><span class="p">(</span><span class="n">ApplicationVersion</span><span class="p">.</span><span class="n">Major</span><span class="p">,</span> <span class="n">ApplicationVersion</span><span class="p">.</span><span class="n">Minor</span><span class="p">,</span> <span class="n">ApplicationVersion</span><span class="p">.</span><span class="n">Build</span><span class="p">,</span> <span class="n">ApplicationDeployment</span><span class="p">.</span><span class="n">CurrentDeployment</span><span class="p">.</span><span class="n">CurrentVersion</span><span class="p">.</span><span class="n">Revision</span><span class="p">);</span>

    <span class="c1">// Display the Version as part of the window title.</span>
    <span class="n">wndMainWindow</span><span class="p">.</span><span class="n">Title</span> <span class="p">+=</span> <span class="n">ApplicationVersion</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><a href="https://gist.github.com/deadlydog/17292d3cd5e6f81409025d843184078e">Here</a> I hard-code the product version that I want displayed in my application in a variable called ApplicationVersion. When the WPF application is launched, I obtain the ClickOnce Revision and append it onto the end of the version number. I then display the version in my application’s window title, but you might want to show it somewhere else, such as in an About window. If you want, you could even display both the full application version and full ClickOnce version.</p>

<p>I hope this blog has helped you further your automation-foo. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="ClickOnce" /><category term="Deploy" /><category term="Build" /><category term="ClickOnce" /><category term="Continuous" /><category term="Deploy" /><category term="Publish" /><category term="VSTS" /><summary type="html"><![CDATA[ClickOnce applications are a great and easy way to distribute your applications to many users, and have the advantage of offering automatic application updates out-of-the-box. Even though ClickOnce applications are super easy to deploy from Visual Studio (literally 3 clicks, just click Build –&gt; Publish –&gt; Finish), you may still want to have your build system publish the updates for various reasons, such as:]]></summary></entry><entry><title type="html">Creating A .PFX Certificate And Applying It On The Build Server At Build Time</title><link href="https://blog.danskingdom.com/creating-a-pfx-certificate-and-applying-it-on-the-build-server-at-build-time/" rel="alternate" type="text/html" title="Creating A .PFX Certificate And Applying It On The Build Server At Build Time" /><published>2016-12-01T08:46:41+00:00</published><updated>2016-12-01T08:46:41+00:00</updated><id>https://blog.danskingdom.com/creating-a-pfx-certificate-and-applying-it-on-the-build-server-at-build-time</id><content type="html" xml:base="https://blog.danskingdom.com/creating-a-pfx-certificate-and-applying-it-on-the-build-server-at-build-time/"><![CDATA[<p>There are many project types that require a .pfx file in order to build and/or publish successfully, such as ClickOnce and UWP (Universal Windows Platform) applications. When you build these projects locally in Visual Studio everything is fine and dandy, but when you try to build them on a build server (such as part of a Continuous Integration build) the build server may fail with an error like:</p>

<blockquote>
  <p>Error APPX0108: The certificate specified has expired. For more information about renewing certificates, see <a href="http://go.microsoft.com/fwlink/?LinkID=241478">http://go.microsoft.com/fwlink/?LinkID=241478</a>.</p>
</blockquote>

<blockquote>
  <p>error MSB4018: The “ResolveKeySource” task failed unexpectedly.
System.InvalidOperationException: Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.</p>
</blockquote>

<blockquote>
  <p>Cannot import the following key file: companyname.pfx. The key file may be password protected.</p>
</blockquote>

<p>These errors will often occur when you are still using the temporary file that was generated automatically when the project was created in Visual Studio, or when using a .pfx file that is password protected.</p>

<p>If your .pfx file is password protected, we can import the certificate at build time, as shown further below. The nice thing about this approach is it does not require you (or one of your company admins) to manually install the certificate on every build server, <a href="http://stackoverflow.com/questions/1056997/team-foundation-server-build-with-password-protected-codesigning-fails">as</a> <a href="http://stackoverflow.com/questions/4025316/signing-assemblies-with-pfx-files-in-msbuild-team-build-and-tfs">many</a> <a href="http://stackoverflow.com/questions/2815366/cannot-import-the-keyfile-blah-pfx-error-the-keyfile-may-be-password-protec">other</a> <a href="http://chamindac.blogspot.ca/2014/02/tfs-build-with-password-protected-pfx.html">sites</a> <a href="https://blogs.msdn.microsoft.com/nagarajp/2005/11/08/using-password-protected-signing-keys-in-teambuild/">suggest</a>. This way you don’t have to bother your admin or get unexpected broken builds when a new build server is added to the pool.</p>

<p>If your .pfx file has expired, you need to remove the current pfx file and add a new (password-protected) one in its place.</p>

<h2 id="creating-a-new-pfx-file-for-a-uwp-application">Creating a new .pfx file for a UWP application</h2>

<p>To create a new password protected .pfx file in a UWP application,</p>

<ol>
  <li>open the <em>Package.appxmanifest</em> file that resides in the root of the project and</li>
  <li>go to the <em>Packaging</em> tab.</li>
  <li>In here, click the <em>Choose Certificate…</em> button.</li>
  <li>In the <em>Configure Certificate…</em> dropdown, choose an existing certificate that you have, or create a new test certificate and provide a password for it. This should create the certificate and add the new .pfx file to your project’s root.</li>
</ol>

<p><img src="/assets/Posts/2016/12/Create-Pfx-Certificate-In-UWP-App.png" alt="Create Pfx Certificate In UWP App" /></p>

<h2 id="creating-a-new-pfx-file-for-a-clickonce-application">Creating a new .pfx file for a ClickOnce application</h2>

<p>Creating a pfx file for a ClickOnce application is similar, but instead you want to</p>

<ol>
  <li>open up the project’s <em>Properties</em> window (by right-clicking on the project) and</li>
  <li>go to the <em>Signing</em> tab.</li>
  <li>Check off the box to <em>Sign the ClickOnce manifests</em> and then</li>
  <li>choose the certificate you want to use, or create your own password-protected test certificate.</li>
</ol>

<p><img src="/assets/Posts/2016/12/Create-Pfx-Certificate-In-ClickOnce-App.png" alt="Create Pfx Certificate In ClickOnce App" /></p>

<h2 id="applying-the-pfx-file-before-building-on-the-build-server">Applying the .pfx file before building on the build server</h2>

<p>Now that the project has a pfx certificate, we will need to update our build server to apply it before attempting to build the solution, otherwise a certificate related error will likely be thrown.</p>

<p>Before building the solution, we will want to apply the certificate using this PowerShell script, <em>Import-PfxCertificate.ps1</em>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">param</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$PfxFilePath</span><span class="p">,</span><span class="w"> </span><span class="nv">$Password</span><span class="p">)</span><span class="w">

</span><span class="c"># You may provide a [string] or a [SecureString] for the $Password parameter.</span><span class="w">

</span><span class="nv">$absolutePfxFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Resolve-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$PfxFilePath</span><span class="w">
</span><span class="n">Write-Output</span><span class="w"> </span><span class="s2">"Importing store certificate &amp;#39;</span><span class="nv">$absolutePfxFilePath</span><span class="s2">&amp;#39;..."</span><span class="w">

</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Security</span><span class="w">
</span><span class="nv">$cert</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Security.Cryptography.X509Certificates.X509Certificate2</span><span class="w">
</span><span class="nv">$cert</span><span class="o">.</span><span class="nf">Import</span><span class="p">(</span><span class="nv">$absolutePfxFilePath</span><span class="p">,</span><span class="w"> </span><span class="nv">$Password</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">System.Security.Cryptography.X509Certificates.X509KeyStorageFlags</span><span class="p">]::</span><span class="nx">PersistKeySet</span><span class="p">)</span><span class="w">
</span><span class="nv">$store</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new-object</span><span class="w"> </span><span class="nx">system.security.cryptography.X509Certificates.X509Store</span><span class="w"> </span><span class="nt">-argumentlist</span><span class="w"> </span><span class="s2">"MY"</span><span class="p">,</span><span class="w"> </span><span class="nx">CurrentUser</span><span class="w">
</span><span class="nv">$store</span><span class="o">.</span><span class="nf">Open</span><span class="p">([</span><span class="n">System.Security.Cryptography.X509Certificates.OpenFlags</span><span class="p">]::</span><span class="s2">"ReadWrite"</span><span class="p">)</span><span class="w">
</span><span class="nv">$store</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$cert</span><span class="p">)</span><span class="w">
</span><span class="nv">$store</span><span class="o">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></code></pre></div></div>

<p>You can <a href="https://gist.github.com/deadlydog/9f87fba75d611b4f1757af7767aa2d05">download the Import-PfxCertificate Powershell script from here</a>.</p>

<p>To do this you will need to save this <em>Import-PfxCertificate.ps1</em> file in your version control repository so that the build system has access to it. In my projects I have a root <em>buildTools</em> directory that I store these types of build scripts in. In your build system, you will need to configure a new step to run the PowerShell script before the step to actually build your solution. When you call the script you will need to pass it the path to the .pfx file (which should also be checked into source control), and the password for the .pfx file. If your build system supports using “hidden”/”private” variables, then it is recommended you use them to keep the .pfx file password a secret.</p>

<p>Here I am using VSTS (Visual Studio Team Services), but the same steps should generally apply to any build system (e.g. TeamCity). I have created a new build step called <em>Apply Store Certificate</em> that calls the Import-PfxCertificate.ps1 PowerShell script. This step is set to occur before the <em>Build solution</em> step, and provides the required parameters to the script; the path to the .pfx file and the certificates password (as a hidden variable that I configured in the build system). Notice that I also set the <em>Working folder</em> that the script will run from to the directory that the .pfx resides in, so that the script will be able to resolve the absolute file path.</p>

<p><img src="/assets/Posts/2016/12/VSTS-Build-Pfx-Certificate-Step.png" alt="VSTS Build Pfx Certificate Step" /></p>

<p>Your build system should now be able to apply the certificate before building the solution, avoiding the certificate errors and hopefully resulting in a successful build.</p>

<p>Hopefully this post has helped you out. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="ClickOnce" /><category term="UWP" /><category term="Visual Studio" /><category term="Build Server" /><category term="Certificate" /><category term="compile" /><category term="PFX" /><summary type="html"><![CDATA[There are many project types that require a .pfx file in order to build and/or publish successfully, such as ClickOnce and UWP (Universal Windows Platform) applications. When you build these projects locally in Visual Studio everything is fine and dandy, but when you try to build them on a build server (such as part of a Continuous Integration build) the build server may fail with an error like:]]></summary></entry><entry><title type="html">Limit The Number Of C# Tasks That Run In Parallel</title><link href="https://blog.danskingdom.com/limit-the-number-of-c-tasks-that-run-in-parallel/" rel="alternate" type="text/html" title="Limit The Number Of C# Tasks That Run In Parallel" /><published>2016-04-17T08:46:32+00:00</published><updated>2016-04-29T00:00:00+00:00</updated><id>https://blog.danskingdom.com/limit-the-number-of-c-tasks-that-run-in-parallel</id><content type="html" xml:base="https://blog.danskingdom.com/limit-the-number-of-c-tasks-that-run-in-parallel/"><![CDATA[<h2 id="why-i-needed-to-throttle-the-number-of-tasks-running-simultaneously">Why I needed to throttle the number of Tasks running simultaneously</h2>

<p>In the past few months I have come across the scenario where I wanted to run a whole bunch of Tasks (potentially thousands), but didn’t necessarily want to run all (or even a lot) of them in parallel at the same time. In my scenarios I was using the Tasks to make web requests; not CPU-heavy work, otherwise I would have opted for using Parallel.Foreach.</p>

<p>The first time I encountered this problem, it was because my application would be running on the cheapest VM that I could get from AWS; this meant a server with 1 slow CPU and less than 1GB of RAM.  Telling that server to spin up 100 threads simultaneously likely would not end very well. I realize that the OS determines how many threads to run at a time, so likely not all 100 threads would run concurrently, but having the ability to specify a lower maximum than the OS would use gives us more control over bulkheading our application to make sure it plays nice and does not consume too many server resources.</p>

<p>The second time, I needed to request information from one of our company’s own web services. The web service used pagination for retrieving a list of user. There was no endpoint that would give me all users in one shot; instead I had to request the users on page 1, page 2, page 3, etc. until I reached the last page. In this case, my concern was around DOSing (Denial of Service) our own web service. If I created 500 web request Tasks to retrieve the users from 500 pages and made all of the requests simultaneously, I risked putting a lot of stress on my web service, and potentially on my network as well.</p>

<p>In both of these cases I was looking for a solution that would still complete all of the Tasks I created, but would allow me to specify that a maximum of, say 5, should run at the same time.</p>

<h2 id="what-the-code-to-run-a-bunch-of-tasks-typically-looks-like">What the code to run a bunch of Tasks typically looks like</h2>

<p>Let’s say you have a function that you want to run a whole bunch of times concurrently in separate Tasks:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">DoSomething</span><span class="p">(</span><span class="kt">int</span> <span class="n">taskNumber</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">1</span><span class="p">));</span>
    <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"Task Number: "</span> <span class="p">+</span> <span class="n">taskNumber</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here is how you typically might start up 100 Tasks to do something:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">DoSomethingALotWithTasks</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">listOfTasks</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;();</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">100</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">count</span> <span class="p">=</span> <span class="n">i</span><span class="p">;</span>
        <span class="c1">// Note that we start the Task here too.</span>
        <span class="n">listOfTasks</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="nf">Something</span><span class="p">(</span><span class="n">count</span><span class="p">)));</span>
    <span class="p">}</span>
    <span class="n">Task</span><span class="p">.</span><span class="nf">WaitAll</span><span class="p">(</span><span class="n">listOfTasks</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="what-the-code-to-run-a-bunch-of-tasks-and-throttle-how-many-are-ran-concurrently-looks-like">What the code to run a bunch of Tasks and throttle how many are ran concurrently looks like</h2>

<p>Here is how you would run those same tasks using the throttling function I provide further down, limiting it to running at most 3 Tasks simultaneously.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">void</span> <span class="nf">DoSomethingALotWithTasksThrottled</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">listOfTasks</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;();</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">100</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">count</span> <span class="p">=</span> <span class="n">i</span><span class="p">;</span>
        <span class="c1">// Note that we create the Task here, but do not start it.</span>
        <span class="n">listOfTasks</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">Task</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="nf">Something</span><span class="p">(</span><span class="n">count</span><span class="p">)));</span>
    <span class="p">}</span>
    <span class="n">Tasks</span><span class="p">.</span><span class="nf">StartAndWaitAllThrottled</span><span class="p">(</span><span class="n">listOfTasks</span><span class="p">,</span> <span class="m">3</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="gimme-the-code-to-limit-my-concurrent-tasks">Gimme the code to limit my concurrent Tasks!</h2>

<p>Because I needed this solution in different projects, I created a nice generic, reusable function for it. I’m presenting the functions here, and they can also be found in <a href="https://dansutilitylibraries.codeplex.com/SourceControl/latest#DansUtilityLibraries/DansCSharpLibrary/Threading/Tasks.cs">my own personal open-source utility library here</a>.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel.</span>
<span class="c1">/// &lt;para&gt;NOTE: If one of the given tasks has already been started, an exception will be thrown.&lt;/para&gt;</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="c1">/// &lt;param name="tasksToRun"&gt;The tasks to run.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="maxActionsToRunInParallel"&gt;The maximum number of tasks to run in parallel.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="cancellationToken"&gt;The cancellation token.&lt;/param&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">StartAndWaitAllThrottled</span><span class="p">(</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;</span> <span class="n">tasksToRun</span><span class="p">,</span> <span class="kt">int</span> <span class="n">maxActionsToRunInParallel</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationToken</span><span class="p">())</span>
<span class="p">{</span>
    <span class="nf">StartAndWaitAllThrottled</span><span class="p">(</span><span class="n">tasksToRun</span><span class="p">,</span> <span class="n">maxActionsToRunInParallel</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Starts the given tasks and waits for them to complete. This will run the specified number of tasks in parallel.</span>
<span class="c1">/// &lt;para&gt;NOTE: If a timeout is reached before the Task completes, another Task may be started, potentially running more than the specified maximum allowed.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;NOTE: If one of the given tasks has already been started, an exception will be thrown.&lt;/para&gt;</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="c1">/// &lt;param name="tasksToRun"&gt;The tasks to run.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="maxActionsToRunInParallel"&gt;The maximum number of tasks to run in parallel.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="timeoutInMilliseconds"&gt;The maximum milliseconds we should allow the max tasks to run in parallel before allowing another task to start. Specify -1 to wait indefinitely.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="cancellationToken"&gt;The cancellation token.&lt;/param&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">StartAndWaitAllThrottled</span><span class="p">(</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;</span> <span class="n">tasksToRun</span><span class="p">,</span> <span class="kt">int</span> <span class="n">maxActionsToRunInParallel</span><span class="p">,</span> <span class="kt">int</span> <span class="n">timeoutInMilliseconds</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationToken</span><span class="p">())</span>
<span class="p">{</span>
    <span class="c1">// Convert to a list of tasks so that we don't enumerate over it multiple times needlessly.</span>
    <span class="kt">var</span> <span class="n">tasks</span> <span class="p">=</span> <span class="n">tasksToRun</span><span class="p">.</span><span class="nf">ToList</span><span class="p">();</span>

    <span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">throttler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SemaphoreSlim</span><span class="p">(</span><span class="n">maxActionsToRunInParallel</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">postTaskTasks</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;();</span>

        <span class="c1">// Have each task notify the throttler when it completes so that it decrements the number of tasks currently running.</span>
        <span class="n">tasks</span><span class="p">.</span><span class="nf">ForEach</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">postTaskTasks</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="nf">ContinueWith</span><span class="p">(</span><span class="n">tsk</span> <span class="p">=&gt;</span> <span class="n">throttler</span><span class="p">.</span><span class="nf">Release</span><span class="p">())));</span>

        <span class="c1">// Start running each task.</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">task</span> <span class="k">in</span> <span class="n">tasks</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="c1">// Increment the number of tasks currently running and wait if too many are running.</span>
            <span class="n">throttler</span><span class="p">.</span><span class="nf">Wait</span><span class="p">(</span><span class="n">timeoutInMilliseconds</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>

            <span class="n">cancellationToken</span><span class="p">.</span><span class="nf">ThrowIfCancellationRequested</span><span class="p">();</span>
            <span class="n">task</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="c1">// Wait for all of the provided tasks to complete.</span>
        <span class="c1">// We wait on the list of "post" tasks instead of the original tasks, otherwise there is a potential race condition where the throttler&amp;amp;amp;#39;s using block is exited before some Tasks have had their "post" action completed, which references the throttler, resulting in an exception due to accessing a disposed object.</span>
        <span class="n">Task</span><span class="p">.</span><span class="nf">WaitAll</span><span class="p">(</span><span class="n">postTaskTasks</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">(),</span> <span class="n">cancellationToken</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Above I have them defined as static functions on my own Tasks class, but you can define them however you like. Notice that the functions also Start the Tasks, so <strong>you should not start them before passing them into these functions</strong>, otherwise an exception will be thrown when it tries to restart a Task. The last thing to note is you will need to include the <code class="language-plaintext highlighter-rouge">System.Threading</code> and <code class="language-plaintext highlighter-rouge">System.Threading.Tasks</code> namespaces.</p>

<p>Here are the async equivalents of the above functions, to make it easy to not block the UI thread while waiting for your tasks to complete:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel.</span>
<span class="c1">/// &lt;para&gt;NOTE: If one of the given tasks has already been started, an exception will be thrown.&lt;/para&gt;</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="c1">/// &lt;param name="tasksToRun"&gt;The tasks to run.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="maxTasksToRunInParallel"&gt;The maximum number of tasks to run in parallel.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="cancellationToken"&gt;The cancellation token.&lt;/param&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">StartAndWaitAllThrottledAsync</span><span class="p">(</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;</span> <span class="n">tasksToRun</span><span class="p">,</span> <span class="kt">int</span> <span class="n">maxTasksToRunInParallel</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationToken</span><span class="p">())</span>
<span class="p">{</span>
    <span class="k">await</span> <span class="nf">StartAndWaitAllThrottledAsync</span><span class="p">(</span><span class="n">tasksToRun</span><span class="p">,</span> <span class="n">maxTasksToRunInParallel</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Starts the given tasks and waits for them to complete. This will run the specified number of tasks in parallel.</span>
<span class="c1">/// &lt;para&gt;NOTE: If a timeout is reached before the Task completes, another Task may be started, potentially running more than the specified maximum allowed.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;NOTE: If one of the given tasks has already been started, an exception will be thrown.&lt;/para&gt;</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="c1">/// &lt;param name="tasksToRun"&gt;The tasks to run.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="maxTasksToRunInParallel"&gt;The maximum number of tasks to run in parallel.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="timeoutInMilliseconds"&gt;The maximum milliseconds we should allow the max tasks to run in parallel before allowing another task to start. Specify -1 to wait indefinitely.&lt;/param&gt;</span>
<span class="c1">/// &lt;param name="cancellationToken"&gt;The cancellation token.&lt;/param&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">StartAndWaitAllThrottledAsync</span><span class="p">(</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;</span> <span class="n">tasksToRun</span><span class="p">,</span> <span class="kt">int</span> <span class="n">maxTasksToRunInParallel</span><span class="p">,</span> <span class="kt">int</span> <span class="n">timeoutInMilliseconds</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CancellationToken</span><span class="p">())</span>
<span class="p">{</span>
    <span class="c1">// Convert to a list of tasks so that we don't enumerate over it multiple times needlessly.</span>
    <span class="kt">var</span> <span class="n">tasks</span> <span class="p">=</span> <span class="n">tasksToRun</span><span class="p">.</span><span class="nf">ToList</span><span class="p">();</span>

    <span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">throttler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SemaphoreSlim</span><span class="p">(</span><span class="n">maxTasksToRunInParallel</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">postTaskTasks</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Task</span><span class="p">&gt;();</span>

        <span class="c1">// Have each task notify the throttler when it completes so that it decrements the number of tasks currently running.</span>
        <span class="n">tasks</span><span class="p">.</span><span class="nf">ForEach</span><span class="p">(</span><span class="n">t</span> <span class="p">=&gt;</span> <span class="n">postTaskTasks</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="nf">ContinueWith</span><span class="p">(</span><span class="n">tsk</span> <span class="p">=&gt;</span> <span class="n">throttler</span><span class="p">.</span><span class="nf">Release</span><span class="p">())));</span>

        <span class="c1">// Start running each task.</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">task</span> <span class="k">in</span> <span class="n">tasks</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="c1">// Increment the number of tasks currently running and wait if too many are running.</span>
            <span class="k">await</span> <span class="n">throttler</span><span class="p">.</span><span class="nf">WaitAsync</span><span class="p">(</span><span class="n">timeoutInMilliseconds</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>

            <span class="n">cancellationToken</span><span class="p">.</span><span class="nf">ThrowIfCancellationRequested</span><span class="p">();</span>
            <span class="n">task</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="c1">// Wait for all of the provided tasks to complete.</span>
        <span class="c1">// We wait on the list of "post" tasks instead of the original tasks, otherwise there is a potential race condition where the throttler&amp;amp;amp;#39;s using block is exited before some Tasks have had their "post" action completed, which references the throttler, resulting in an exception due to accessing a disposed object.</span>
        <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">WhenAll</span><span class="p">(</span><span class="n">postTaskTasks</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And if you don’t believe me that this works, you can <a href="/assets/Posts/2016/04/LimitNumberOfSimultaneousTasksExample.zip">take a look at this sample project and run the code for yourself</a>.</p>

<h2 id="update-2016-04-29">Update: 2016-04-29</h2>

<p>Shortly after publishing this post I discovered <a href="https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.invoke%28v=vs.110%29.aspx">Parallel.Invoke</a>, which can also throttle the number of threads, but takes <a href="https://msdn.microsoft.com/en-us/library/system.action%28v=vs.110%29.aspx">Actions</a> as input instead of <a href="https://msdn.microsoft.com/en-us/library/system.threading.tasks.task%28v=vs.110%29.aspx">Tasks</a>. Here’s an example of how to limit the number of threads using Parallel.Invoke:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">DoSomethingALotWithActionsThrottled</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">listOfActions</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Action</span><span class="p">&gt;();</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">100</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">count</span> <span class="p">=</span> <span class="n">i</span><span class="p">;</span>
        <span class="c1">// Note that we create the Action here, but do not start it.</span>
        <span class="n">listOfActions</span><span class="p">.</span><span class="nf">Add</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="nf">DoSomething</span><span class="p">(</span><span class="n">count</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="kt">var</span> <span class="n">options</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ParallelOptions</span> <span class="p">{</span><span class="n">MaxDegreeOfParallelism</span> <span class="p">=</span> <span class="m">3</span><span class="p">};</span>
    <span class="n">Parallel</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">(</span><span class="n">options</span><span class="p">,</span> <span class="n">listOfActions</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice that you define the max number of threads to run simultaneously by using the <a href="https://msdn.microsoft.com/en-us/library/system.threading.tasks.paralleloptions%28v=vs.110%29.aspx">ParallelOptions</a> classes MaxDegreeOfParallelism property, which also accepts a CancellationToken if needed. This method is nice because it doesn’t require having the additional custom code; it’s all built into .Net. However, it does mean dealing with Actions instead of Tasks, which isn’t a bad thing at all, but you may have a personal preference of which one you prefer to work with. Also, Tasks can offer additional functionality, such as <a href="https://msdn.microsoft.com/en-us/library/dd270696%28v=vs.110%29.aspx">ContinueWith()</a>, and Parallel.Invoke does not provide an asynchronous version, but my functions do. According to <a href="https://msdn.microsoft.com/en-us/library/ff963549.aspx">this MSDN page</a>, Parallel.Invoke uses Task.WaitAll() under the hood, so they should be equivalent performance-wise, and there shouldn’t be any situations where using one is preferable over the other. <a href="https://msdn.microsoft.com/en-us/library/dd537609%28v=vs.110%29.aspx">This other MSDN page</a> goes into detail about Tasks, and also mentions using Parallel.Invoke near the start.</p>

<p>I hope you find this information useful. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="CSharp" /><category term="CSharp" /><category term="Task" /><category term="Thread" /><category term="Throttle" /><category term="Limit" /><category term="Maximum" /><category term="Simultaneous" /><category term="Concurrent" /><category term="Parallel" /><summary type="html"><![CDATA[Why I needed to throttle the number of Tasks running simultaneously]]></summary></entry><entry><title type="html">Fix MSBuild 2015 Compile Taking A Very Long Time</title><link href="https://blog.danskingdom.com/fix-msbuild-2015-compile-taking-a-very-long-time/" rel="alternate" type="text/html" title="Fix MSBuild 2015 Compile Taking A Very Long Time" /><published>2016-04-14T06:32:24+00:00</published><updated>2016-04-14T06:32:24+00:00</updated><id>https://blog.danskingdom.com/fix-msbuild-2015-compile-taking-a-very-long-time</id><content type="html" xml:base="https://blog.danskingdom.com/fix-msbuild-2015-compile-taking-a-very-long-time/"><![CDATA[<p>I created the <a href="https://invokemsbuild.codeplex.com/">Invoke-MsBuild PowerShell Module</a> (also <a href="https://www.powershellgallery.com/packages/Invoke-MsBuild/">available in the PowerShell Gallery</a>), and recently added support to use the Visual Studio 2015 version of MsBuild.exe, when available. After doing so, I noticed that sometimes the build would take a very long time to complete; a solution that typically would take 10 seconds to compile was all of a sudden taking 10 minutes. When changing Invoke-MsBuild back to defaulting to the Visual Studio 2013 version of MsBuild.exe the problem went away. I thought that maybe there was just something strange with my workstation, however after updating Invoke-MsBuild on our build servers at my work we saw the same thing there.</p>

<p>Luckily, a fellow by the name of Jens Doose contacted me saying that he was experiencing the same problem when using Invoke-MsBuild, and also that he had fixed it. The solution ended up being that when calling MsBuild.exe, we had to specify an additional command-line argument of <strong>/p:UseSharedConfiguration=false</strong>.</p>

<blockquote>
  <p>msbuild.exe someSolution.sln /p:Configuration=Release /p:UseSharedConfiguration=false</p>
</blockquote>

<p>I’m not sure what the other implications of providing this UseSharedConfiguration parameter are as I can’t find any documentation of it online, and I’m not really sure how Jens came across it. It does seem to solve the problem of compiling take a long time though, and I haven’t noticed any other side effects, so I’m going to stick with it.</p>

<p>If you run into the same problem with msbuild.exe and this helps you out, leave a comment to let me know. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="MSBuild" /><category term="MSBuild" /><category term="msbuild.exe" /><category term="Slow" /><category term="Hangs" /><category term="Build" /><category term="Compile" /><summary type="html"><![CDATA[I created the Invoke-MsBuild PowerShell Module (also available in the PowerShell Gallery), and recently added support to use the Visual Studio 2015 version of MsBuild.exe, when available. After doing so, I noticed that sometimes the build would take a very long time to complete; a solution that typically would take 10 seconds to compile was all of a sudden taking 10 minutes. When changing Invoke-MsBuild back to defaulting to the Visual Studio 2013 version of MsBuild.exe the problem went away. I thought that maybe there was just something strange with my workstation, however after updating Invoke-MsBuild on our build servers at my work we saw the same thing there.]]></summary></entry><entry><title type="html">Module to Synchronously Zip and Unzip using PowerShell 2.0</title><link href="https://blog.danskingdom.com/module-to-synchronously-zip-and-unzip-using-powershell-2-0/" rel="alternate" type="text/html" title="Module to Synchronously Zip and Unzip using PowerShell 2.0" /><published>2015-05-02T22:04:48+00:00</published><updated>2015-05-02T22:04:48+00:00</updated><id>https://blog.danskingdom.com/module-to-synchronously-zip-and-unzip-using-powershell-2-0</id><content type="html" xml:base="https://blog.danskingdom.com/module-to-synchronously-zip-and-unzip-using-powershell-2-0/"><![CDATA[<p>If you search for ways to zip and unzip files using PowerShell, you will find that there a lot of different methods. Some people <a href="http://stackoverflow.com/a/13302548/602585">invoke .Net 4.5 assembly methods</a>, others call a 3rd party executable (I’ve shown how to do this in <a href="https://blog.danskingdom.com/powershell-function-to-create-a-password-protected-zip-file/">one of my other posts</a>). For my needs this time around I required a method that didn’t involve <a href="http://stackoverflow.com/a/1153144/602585">using 3rd party tools</a>, and wanted my PowerShell script to work on any Windows OS, not just ones that had .Net 4.5 installed (which isn’t available for older OSs like Windows XP).</p>

<p>I quickly found what I was after; you can <a href="http://serverfault.com/a/201604">use the OS native Application.Shell object</a>. This is what Windows/File Explorer uses behind the scenes when you copy/cut/paste a file. The problem though was that the operations all happen asynchronously, so there was no way for me to determine when the Zip operation actually completed. This was a problem, as I wanted my PowerShell script to copy the zip file to a network location once all of the files had been zipped up, and perform other operations on files once they were unzipped from a different zip file, and if I’m zipping/unzipping many MB or GBs or data, the operation might take several minutes. Most examples I found online worked around this by just putting a Start-Sleep –Seconds 10 after the call to create or extract the Zip files. That’s a super simple solution, and it works, but I wasn’t always sure how large the directory that I wanted to zip/unzip was going to be, and didn’t want to have my script sleep for 5 minutes when the zip/unzip operation sometimes only takes half a second. This is what led to me creating the following PowerShell module below.</p>

<p>This module allows you to add files and directories to a new or existing zip file, as well as to extract the contents of a zip file. Also, it will block script execution until the zip/unzip operation completes.</p>

<p>You can <a href="/assets/Posts/2015/05/Synchronous-ZipAndUnzip.zip">download the PowerShell module zip here</a> or get it from <a href="https://gist.github.com/deadlydog/4d3d98ca10c5c6b62e29f7e793850305">the gist here</a>.</p>

<p>Here is an example of how to call the 2 public module functions, Compress-ZipFile (i.e. Zip) and Expand-ZipFile (i.e. Unzip):</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># If you place the psm1 file in the global PowerShell Modules directory then you could reference it just by name, not by the entire file path like we do here (assumes psm1 file is in same directory as your script).</span><span class="w">
</span><span class="nv">$THIS_SCRIPTS_DIRECTORY_PATH</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nv">$</span><span class="nn">script</span><span class="p">:</span><span class="nv">MyInvocation</span><span class="o">.</span><span class="nf">MyCommand</span><span class="o">.</span><span class="nf">Path</span><span class="w">
</span><span class="nv">$SynchronousZipAndUnzipModulePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$THIS_SCRIPTS_DIRECTORY_PATH</span><span class="w"> </span><span class="s1">'Synchronous-ZipAndUnzip.psm1'</span><span class="w">

</span><span class="c"># Import the Synchronous-ZipAndUnzip module.</span><span class="w">
</span><span class="n">Import-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$SynchronousZipAndUnzipModulePath</span><span class="w">

</span><span class="c"># Variables used to test the functions.</span><span class="w">
</span><span class="nv">$zipFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Temp\ZipFile.zip"</span><span class="w">
</span><span class="nv">$filePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Test.txt"</span><span class="w">
</span><span class="nv">$directoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Test\ZipMeUp"</span><span class="w">
</span><span class="nv">$destinationDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Temp\UnzippedContents"</span><span class="w">

</span><span class="c"># Create a new Zip file that contains only Test.txt.</span><span class="w">
</span><span class="n">Compress-ZipFile</span><span class="w"> </span><span class="nt">-ZipFilePath</span><span class="w"> </span><span class="nv">$zipFilePath</span><span class="w"> </span><span class="nt">-FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nv">$filePath</span><span class="w"> </span><span class="nt">-OverwriteWithoutPrompting</span><span class="w">

</span><span class="c"># Add the ZipMeUp directory to the zip file.</span><span class="w">
</span><span class="n">Compress-ZipFile</span><span class="w"> </span><span class="nt">-ZipFilePath</span><span class="w"> </span><span class="nv">$zipFilePath</span><span class="w"> </span><span class="nt">-FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nv">$directoryPath</span><span class="w"> </span><span class="nt">-OverwriteWithoutPrompting</span><span class="w">

</span><span class="c"># Unzip the Zip file to a new UnzippedContents directory.</span><span class="w">
</span><span class="n">Expand-ZipFile</span><span class="w"> </span><span class="nt">-ZipFilePath</span><span class="w"> </span><span class="nv">$zipFilePath</span><span class="w"> </span><span class="nt">-DestinationDirectoryPath</span><span class="w"> </span><span class="nv">$destinationDirectoryPath</span><span class="w"> </span><span class="nt">-OverwriteWithoutPrompting</span><span class="w">
</span></code></pre></div></div>

<p>And here is the Synchronous-ZipAndUnzip.psm1 module code itself:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#Requires -Version 2.0</span><span class="w">

</span><span class="c"># Recursive function to calculate the total number of files and directories in the Zip file.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">GetNumberOfItemsInZipFileItems</span><span class="p">(</span><span class="nv">$shellItems</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$totalItems</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$shellItems</span><span class="o">.</span><span class="nf">Count</span><span class="w">
    </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$shellItem</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$shellItems</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$shellItem</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nv">$totalItems</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">GetNumberOfItemsInZipFileItems</span><span class="w"> </span><span class="nt">-shellItems</span><span class="w"> </span><span class="nv">$shellItem</span><span class="o">.</span><span class="nf">GetFolder</span><span class="o">.</span><span class="nf">Items</span><span class="p">()</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="nv">$totalItems</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Recursive function to move a directory into a Zip file, since we can move files out of a Zip file, but not directories, and copying a directory into a Zip file when it already exists is not allowed.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">MoveDirectoryIntoZipFile</span><span class="p">(</span><span class="nv">$parentInZipFileShell</span><span class="p">,</span><span class="w"> </span><span class="nv">$pathOfItemToCopy</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Get the name of the file/directory to copy, and the item itself.</span><span class="w">
    </span><span class="nv">$nameOfItemToCopy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$pathOfItemToCopy</span><span class="w"> </span><span class="nt">-Leaf</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$parentInZipFileShell</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nv">$parentInZipFileShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$parentInZipFileShell</span><span class="o">.</span><span class="nf">GetFolder</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="nv">$itemToCopyShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$parentInZipFileShell</span><span class="o">.</span><span class="nf">ParseName</span><span class="p">(</span><span class="nv">$nameOfItemToCopy</span><span class="p">)</span><span class="w">

    </span><span class="c"># If this item does not exist in the Zip file yet, or it is a file, move it over.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$itemToCopyShell</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="o">!</span><span class="nv">$itemToCopyShell</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$parentInZipFileShell</span><span class="o">.</span><span class="nf">MoveHere</span><span class="p">(</span><span class="nv">$pathOfItemToCopy</span><span class="p">)</span><span class="w">

        </span><span class="c"># Wait for the file to be moved before continuing, to avoid errors about the zip file being locked or a file not being found.</span><span class="w">
        </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$pathOfItemToCopy</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="c"># Else this is a directory that already exists in the Zip file, so we need to traverse it and copy each file/directory within it.</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># Copy each file/directory in the directory to the Zip file.</span><span class="w">
        </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$item</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$pathOfItemToCopy</span><span class="w"> </span><span class="nt">-Force</span><span class="p">))</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="n">MoveDirectoryIntoZipFile</span><span class="w"> </span><span class="nt">-parentInZipFileShell</span><span class="w"> </span><span class="nv">$itemToCopyShell</span><span class="w"> </span><span class="nt">-pathOfItemToCopy</span><span class="w"> </span><span class="nv">$item</span><span class="o">.</span><span class="nf">FullName</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Recursive function to move all of the files that start with the File Name Prefix to the Directory To Move Files To.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">MoveFilesOutOfZipFileItems</span><span class="p">(</span><span class="nv">$shellItems</span><span class="p">,</span><span class="w"> </span><span class="nv">$directoryToMoveFilesToShell</span><span class="p">,</span><span class="w"> </span><span class="nv">$fileNamePrefix</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Loop through every item in the file/directory.</span><span class="w">
    </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$shellItem</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$shellItems</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># If this is a directory, recursively call this function to iterate over all files/directories within it.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$shellItem</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$totalItems</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">MoveFilesOutOfZipFileItems</span><span class="w"> </span><span class="nt">-shellItems</span><span class="w"> </span><span class="nv">$shellItem</span><span class="o">.</span><span class="nf">GetFolder</span><span class="o">.</span><span class="nf">Items</span><span class="p">()</span><span class="w"> </span><span class="nt">-directoryToMoveFilesTo</span><span class="w"> </span><span class="nv">$directoryToMoveFilesToShell</span><span class="w"> </span><span class="nt">-fileNameToMatch</span><span class="w"> </span><span class="nv">$fileNameToMatch</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="c"># Else this is a file.</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># If this file name starts with the File Name Prefix, move it to the specified directory.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$shellItem</span><span class="o">.</span><span class="nf">Name</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$fileNamePrefix</span><span class="p">))</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$directoryToMoveFilesToShell</span><span class="o">.</span><span class="nf">MoveHere</span><span class="p">(</span><span class="nv">$shellItem</span><span class="p">)</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Expand-ZipFile</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateScript</span><span class="p">({(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="s1">'.zip'</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringComparison</span><span class="p">]::</span><span class="nx">OrdinalIgnoreCase</span><span class="p">)})]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ZipFilePath</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$DestinationDirectoryPath</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"Force"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="nv">$OverwriteWithoutPrompting</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="kr">BEGIN</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">END</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">PROCESS</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># If a Destination Directory was not given, create one in the same directory as the Zip file, with the same name as the Zip file.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$DestinationDirectoryPath</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$DestinationDirectoryPath</span><span class="o">.</span><span class="nf">Trim</span><span class="p">()</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">Empty</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$zipFileDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$ZipFilePath</span><span class="w"> </span><span class="nt">-Parent</span><span class="w">
            </span><span class="nv">$zipFileNameWithoutExtension</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.Path</span><span class="p">]::</span><span class="n">GetFileNameWithoutExtension</span><span class="p">(</span><span class="nv">$ZipFilePath</span><span class="p">)</span><span class="w">
            </span><span class="nv">$DestinationDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$zipFileDirectoryPath</span><span class="w"> </span><span class="nt">-ChildPath</span><span class="w"> </span><span class="nv">$zipFileNameWithoutExtension</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># If the directory to unzip the files to does not exist yet, create it.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$DestinationDirectoryPath</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Container</span><span class="p">))</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$DestinationDirectoryPath</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Container</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="c"># Flags and values found at: https://msdn.microsoft.com/en-us/library/windows/desktop/bb759795%28v=vs.85%29.aspx</span><span class="w">
        </span><span class="nv">$FOF_SILENT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="n">x0004</span><span class="w">
        </span><span class="nv">$FOF_NOCONFIRMATION</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="n">x0010</span><span class="w">
        </span><span class="nv">$FOF_NOERRORUI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="n">x0400</span><span class="w">

        </span><span class="c"># Set the flag values based on the parameters provided.</span><span class="w">
        </span><span class="nv">$copyFlags</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OverwriteWithoutPrompting</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nv">$copyFlags</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$FOF_NOCONFIRMATION</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="c">#   { $copyFlags = $FOF_SILENT + $FOF_NOCONFIRMATION + $FOF_NOERRORUI }</span><span class="w">

        </span><span class="c"># Get the Shell object, Destination Directory, and Zip file.</span><span class="w">
        </span><span class="nv">$shell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nt">-ComObject</span><span class="w"> </span><span class="nx">Shell.Application</span><span class="w">
        </span><span class="nv">$destinationDirectoryShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$shell</span><span class="o">.</span><span class="nf">NameSpace</span><span class="p">(</span><span class="nv">$DestinationDirectoryPath</span><span class="p">)</span><span class="w">
        </span><span class="nv">$zipShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$shell</span><span class="o">.</span><span class="nf">NameSpace</span><span class="p">(</span><span class="nv">$ZipFilePath</span><span class="p">)</span><span class="w">

        </span><span class="c"># Start copying the Zip files into the destination directory, using the flags specified by the user. This is an asynchronous operation.</span><span class="w">
        </span><span class="nv">$destinationDirectoryShell</span><span class="o">.</span><span class="nf">CopyHere</span><span class="p">(</span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">Items</span><span class="p">(),</span><span class="w"> </span><span class="nv">$copyFlags</span><span class="p">)</span><span class="w">

        </span><span class="c"># Get the number of files and directories in the Zip file.</span><span class="w">
        </span><span class="nv">$numberOfItemsInZipFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GetNumberOfItemsInZipFileItems</span><span class="w"> </span><span class="nt">-shellItems</span><span class="w"> </span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">Items</span><span class="p">()</span><span class="w">

        </span><span class="c"># The Copy (i.e. unzip) operation is asynchronous, so wait until it is complete before continuing. That is, sleep until the Destination Directory has the same number of files as the Zip file.</span><span class="w">
        </span><span class="kr">while</span><span class="w"> </span><span class="p">((</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$DestinationDirectoryPath</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="o">.</span><span class="nf">Count</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$numberOfItemsInZipFile</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">100</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Compress-ZipFile</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ZipFilePath</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"Force"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="nv">$OverwriteWithoutPrompting</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="kr">BEGIN</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">END</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">PROCESS</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># If a Zip File Path was not given, create one in the same directory as the file/directory being added to the zip file, with the same name as the file/directory.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$ZipFilePath</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$ZipFilePath</span><span class="o">.</span><span class="nf">Trim</span><span class="p">()</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">Empty</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nv">$ZipFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nt">-ChildPath</span><span class="w"> </span><span class="s1">'.zip'</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="c"># If the Zip file to create does not have an extension of .zip (which is required by the shell.application), add it.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$ZipFilePath</span><span class="o">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="s1">'.zip'</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringComparison</span><span class="p">]::</span><span class="nx">OrdinalIgnoreCase</span><span class="p">))</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nv">$ZipFilePath</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s1">'.zip'</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="c"># If the Zip file to add the file to does not exist yet, create it.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$ZipFilePath</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="p">))</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$ZipFilePath</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">File</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="c"># Get the Name of the file or directory to add to the Zip file.</span><span class="w">
        </span><span class="nv">$fileOrDirectoryNameToAddToZipFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nt">-Leaf</span><span class="w">

        </span><span class="c"># Get the number of files and directories to add to the Zip file.</span><span class="w">
        </span><span class="nv">$numberOfFilesAndDirectoriesToAddToZipFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="o">.</span><span class="nf">Count</span><span class="w">

        </span><span class="c"># Get if we are adding a file or directory to the Zip file.</span><span class="w">
        </span><span class="nv">$itemToAddToZipIsAFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Leaf</span><span class="w">

        </span><span class="c"># Get Shell object and the Zip File.</span><span class="w">
        </span><span class="nv">$shell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nt">-ComObject</span><span class="w"> </span><span class="nx">Shell.Application</span><span class="w">
        </span><span class="nv">$zipShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$shell</span><span class="o">.</span><span class="nf">NameSpace</span><span class="p">(</span><span class="nv">$ZipFilePath</span><span class="p">)</span><span class="w">

        </span><span class="c"># We will want to check if we can do a simple copy operation into the Zip file or not. Assume that we can't to start with.</span><span class="w">
        </span><span class="c"># We can if the file/directory does not exist in the Zip file already, or it is a file and the user wants to be prompted on conflicts.</span><span class="w">
        </span><span class="nv">$canPerformSimpleCopyIntoZipFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">

        </span><span class="c"># If the file/directory does not already exist in the Zip file, or it does exist, but it is a file and the user wants to be prompted on conflicts, then we can perform a simple copy into the Zip file.</span><span class="w">
        </span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">ParseName</span><span class="p">(</span><span class="nv">$fileOrDirectoryNameToAddToZipFile</span><span class="p">)</span><span class="w">
        </span><span class="nv">$itemToAddToZipIsAFileAndUserWantsToBePromptedOnConflicts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nv">$itemToAddToZipIsAFile</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$OverwriteWithoutPrompting</span><span class="p">)</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$itemToAddToZipIsAFileAndUserWantsToBePromptedOnConflicts</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$canPerformSimpleCopyIntoZipFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># If we can perform a simple copy operation to get the file/directory into the Zip file.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$canPerformSimpleCopyIntoZipFile</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># Start copying the file/directory into the Zip file since there won't be any conflicts. This is an asynchronous operation.</span><span class="w">
            </span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">CopyHere</span><span class="p">(</span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="p">)</span><span class="w">  </span><span class="c"># Copy Flags are ignored when copying files into a zip file, so can't use them like we did with the Expand-ZipFile function.</span><span class="w">

            </span><span class="c"># The Copy operation is asynchronous, so wait until it is complete before continuing.</span><span class="w">
            </span><span class="c"># Wait until we can see that the file/directory has been created.</span><span class="w">
            </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">ParseName</span><span class="p">(</span><span class="nv">$fileOrDirectoryNameToAddToZipFile</span><span class="p">)</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w"> </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">100</span><span class="w"> </span><span class="p">}</span><span class="w">

            </span><span class="c"># If we are copying a directory into the Zip file, we want to wait until all of the files/directories have been copied.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$itemToAddToZipIsAFile</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># Get the number of files and directories that should be copied into the Zip file.</span><span class="w">
                </span><span class="nv">$numberOfItemsToCopyIntoZipFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="o">.</span><span class="nf">Count</span><span class="w">

                </span><span class="c"># Get a handle to the new directory we created in the Zip file.</span><span class="w">
                </span><span class="nv">$newDirectoryInZipFileShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">ParseName</span><span class="p">(</span><span class="nv">$fileOrDirectoryNameToAddToZipFile</span><span class="p">)</span><span class="w">

                </span><span class="c"># Wait until the new directory in the Zip file has the expected number of files and directories in it.</span><span class="w">
                </span><span class="kr">while</span><span class="w"> </span><span class="p">((</span><span class="n">GetNumberOfItemsInZipFileItems</span><span class="w"> </span><span class="nt">-shellItems</span><span class="w"> </span><span class="nv">$newDirectoryInZipFileShell</span><span class="o">.</span><span class="nf">GetFolder</span><span class="o">.</span><span class="nf">Items</span><span class="p">())</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$numberOfItemsToCopyIntoZipFile</span><span class="p">)</span><span class="w">
                </span><span class="p">{</span><span class="w"> </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">100</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="c"># Else we cannot do a simple copy operation. We instead need to move the files out of the Zip file so that we can merge the directory, or overwrite the file without the user being prompted.</span><span class="w">
        </span><span class="c"># We cannot move a directory into the Zip file if a directory with the same name already exists, as a MessageBox warning is thrown, not a conflict resolution prompt like with files.</span><span class="w">
        </span><span class="c"># We cannot silently overwrite an existing file in the Zip file, as the flags passed to the CopyHere/MoveHere functions seem to be ignored when copying into a Zip file.</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># Create a temp directory to hold our file/directory.</span><span class="w">
            </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w">
            </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="p">([</span><span class="n">System.IO.Path</span><span class="p">]::</span><span class="n">GetTempPath</span><span class="p">())</span><span class="w"> </span><span class="nt">-ChildPath</span><span class="w"> </span><span class="p">([</span><span class="n">System.IO.Path</span><span class="p">]::</span><span class="n">GetRandomFileName</span><span class="p">())</span><span class="w">
            </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Container</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">

            </span><span class="c"># If we will be moving a directory into the temp directory.</span><span class="w">
            </span><span class="nv">$numberOfItemsInZipFilesDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># Get the number of files and directories in the Zip file's directory.</span><span class="w">
                </span><span class="nv">$numberOfItemsInZipFilesDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GetNumberOfItemsInZipFileItems</span><span class="w"> </span><span class="nt">-shellItems</span><span class="w"> </span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="o">.</span><span class="nf">GetFolder</span><span class="o">.</span><span class="nf">Items</span><span class="p">()</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># Start moving the file/directory out of the Zip file and into a temp directory. This is an asynchronous operation.</span><span class="w">
            </span><span class="nv">$tempDirectoryShell</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$shell</span><span class="o">.</span><span class="nf">NameSpace</span><span class="p">(</span><span class="nv">$tempDirectoryPath</span><span class="p">)</span><span class="w">
            </span><span class="nv">$tempDirectoryShell</span><span class="o">.</span><span class="nf">MoveHere</span><span class="p">(</span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="p">)</span><span class="w">

            </span><span class="c"># If we are moving a directory, we need to wait until all of the files and directories in that Zip file's directory have been moved.</span><span class="w">
            </span><span class="nv">$fileOrDirectoryPathInTempDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="nt">-ChildPath</span><span class="w"> </span><span class="nv">$fileOrDirectoryNameToAddToZipFile</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># The Move operation is asynchronous, so wait until it is complete before continuing. That is, sleep until the Destination Directory has the same number of files as the directory in the Zip file.</span><span class="w">
                </span><span class="kr">while</span><span class="w"> </span><span class="p">((</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$fileOrDirectoryPathInTempDirectory</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="o">.</span><span class="nf">Count</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$numberOfItemsInZipFilesDirectory</span><span class="p">)</span><span class="w">
                </span><span class="p">{</span><span class="w"> </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">100</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else we are just moving a file, so we just need to check for when that one file has been moved.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># The Move operation is asynchronous, so wait until it is complete before continuing.</span><span class="w">
                </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$fileOrDirectoryPathInTempDirectory</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w"> </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">100</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># We want to copy the file/directory to add to the Zip file to the same location in the temp directory, so that files/directories are merged.</span><span class="w">
            </span><span class="c"># If we should automatically overwrite files, do it.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OverwriteWithoutPrompting</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w"> </span><span class="n">Copy-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nt">-Destination</span><span class="w"> </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else the user should be prompted on each conflict.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w"> </span><span class="n">Copy-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$FileOrDirectoryPathToAddToZipFile</span><span class="w"> </span><span class="nt">-Destination</span><span class="w"> </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Confirm</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="p">}</span><span class="w">  </span><span class="c"># SilentlyContinue errors to avoid an error for every directory copied.</span><span class="w">

            </span><span class="c"># For whatever reason the zip.MoveHere() function is not able to move empty directories into the Zip file, so we have to put dummy files into these directories</span><span class="w">
            </span><span class="c"># and then remove the dummy files from the Zip file after.</span><span class="w">
            </span><span class="c"># If we are copying a directory into the Zip file.</span><span class="w">
            </span><span class="nv">$dummyFileNamePrefix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Dummy.File'</span><span class="w">
            </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$numberOfDummyFilesCreated</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># Place a dummy file in each of the empty directories so that it gets copied into the Zip file without an error.</span><span class="w">
                </span><span class="nv">$emptyDirectories</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$fileOrDirectoryPathInTempDirectory</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-Directory</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$emptyDirectory</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$emptyDirectories</span><span class="p">)</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$numberOfDummyFilesCreated</span><span class="o">++</span><span class="w">
                    </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="p">(</span><span class="n">Join-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$emptyDirectory</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-ChildPath</span><span class="w"> </span><span class="s2">"</span><span class="nv">$dummyFileNamePrefix$numberOfDummyFilesCreated</span><span class="s2">"</span><span class="p">)</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="n">File</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># If we need to copy a directory back into the Zip file.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$fileOrDirectoryInZipFileShell</span><span class="o">.</span><span class="nf">IsFolder</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">MoveDirectoryIntoZipFile</span><span class="w"> </span><span class="nt">-parentInZipFileShell</span><span class="w"> </span><span class="nv">$zipShell</span><span class="w"> </span><span class="nt">-pathOfItemToCopy</span><span class="w"> </span><span class="nv">$fileOrDirectoryPathInTempDirectory</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else we need to copy a file back into the Zip file.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># Start moving the merged file back into the Zip file. This is an asynchronous operation.</span><span class="w">
                </span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">MoveHere</span><span class="p">(</span><span class="nv">$fileOrDirectoryPathInTempDirectory</span><span class="p">)</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># The Move operation is asynchronous, so wait until it is complete before continuing.</span><span class="w">
            </span><span class="c"># Sleep until all of the files have been moved into the zip file. The MoveHere() function leaves empty directories behind, so we only need to watch for files.</span><span class="w">
            </span><span class="kr">do</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">100</span><span class="w">
                </span><span class="nv">$files</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$fileOrDirectoryPathInTempDirectory</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">!</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w"> </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="nv">$files</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">

            </span><span class="c"># If there are dummy files that need to be moved out of the Zip file.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfDummyFilesCreated</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># Move all of the dummy files out of the supposed-to-be empty directories in the Zip file.</span><span class="w">
                </span><span class="n">MoveFilesOutOfZipFileItems</span><span class="w"> </span><span class="nt">-shellItems</span><span class="w"> </span><span class="nv">$zipShell</span><span class="o">.</span><span class="nf">items</span><span class="p">()</span><span class="w"> </span><span class="nt">-directoryToMoveFilesToShell</span><span class="w"> </span><span class="nv">$tempDirectoryShell</span><span class="w"> </span><span class="nt">-fileNamePrefix</span><span class="w"> </span><span class="nv">$dummyFileNamePrefix</span><span class="w">

                </span><span class="c"># The Move operation is asynchronous, so wait until it is complete before continuing.</span><span class="w">
                </span><span class="c"># Sleep until all of the dummy files have been moved out of the zip file.</span><span class="w">
                </span><span class="kr">do</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="n">Start-Sleep</span><span class="w"> </span><span class="nt">-Milliseconds</span><span class="w"> </span><span class="nx">100</span><span class="w">
                    </span><span class="p">[</span><span class="n">Object</span><span class="p">[]]</span><span class="nv">$files</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">!</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Name</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$dummyFileNamePrefix</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w">
                </span><span class="p">}</span><span class="w"> </span><span class="kr">while</span><span class="w"> </span><span class="p">(</span><span class="nv">$files</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$files</span><span class="o">.</span><span class="nf">Count</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$numberOfDummyFilesCreated</span><span class="p">)</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># Delete the temp directory that we created.</span><span class="w">
            </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$tempDirectoryPath</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Specify which functions should be publicly accessible.</span><span class="w">
</span><span class="n">Export-ModuleMember</span><span class="w"> </span><span class="nt">-Function</span><span class="w"> </span><span class="nx">Expand-ZipFile</span><span class="w">
</span><span class="n">Export-ModuleMember</span><span class="w"> </span><span class="nt">-Function</span><span class="w"> </span><span class="nx">Compress-ZipFile</span><span class="w">
</span></code></pre></div></div>

<p>Of course if you don’t want to reference an external module you could always just copy paste the functions from the module directly into your script and call the functions that way.</p>

<p>Happy coding!</p>

<p>Disclaimer: At the time of this writing I have only tested the module on Windows 8.1, so if you discover problems running it on another version of Windows please let me know.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="functions" /><category term="module" /><category term="PowerShell" /><category term="Synchronous" /><category term="Unzip" /><category term="Zip" /><summary type="html"><![CDATA[If you search for ways to zip and unzip files using PowerShell, you will find that there a lot of different methods. Some people invoke .Net 4.5 assembly methods, others call a 3rd party executable (I’ve shown how to do this in one of my other posts). For my needs this time around I required a method that didn’t involve using 3rd party tools, and wanted my PowerShell script to work on any Windows OS, not just ones that had .Net 4.5 installed (which isn’t available for older OSs like Windows XP).]]></summary></entry><entry><title type="html">Tell Microsoft To Fix The Sql Server Management Studio “Connect to Server” Dialog Position</title><link href="https://blog.danskingdom.com/tell-microsoft-to-fix-the-sql-server-management-studio-connect-to-server-dialog-position/" rel="alternate" type="text/html" title="Tell Microsoft To Fix The Sql Server Management Studio “Connect to Server” Dialog Position" /><published>2014-10-17T16:17:39+00:00</published><updated>2014-10-17T16:17:39+00:00</updated><id>https://blog.danskingdom.com/tell-microsoft-to-fix-the-sql-server-management-studio-connect-to-server-dialog-position</id><content type="html" xml:base="https://blog.danskingdom.com/tell-microsoft-to-fix-the-sql-server-management-studio-connect-to-server-dialog-position/"><![CDATA[<p>If you use Sql Server Management Studio (SSMS) with multiple monitors, you likely run into the issue where the “Connect to Server” dialog window opens up either half or completely off the screen when SSMS is opened on a monitor that is not the primary one (see screenshot below).</p>

<p>Several bugs have been reported for this, and apparently MS thinks it is not really an issue since they have decided to close all of the bugs related to it as “Won’t Fix”. Here’s a quote:</p>

<blockquote>
  <p>We took a look at this bug and triaged it against several others and unfortunately, it did not meet the bar to be fixed and we are closing it as ‘won’t fix’.</p>
</blockquote>

<p>Why they admit that it is a problem and close it as “Won’t Fix” instead of just leaving it open with a low priority is beyond me.</p>

<p>What’s even more surprising though is that these issues currently have less than 10 upvotes! Let’s fix that. Like many people, I use SSMS daily, and this is easily my biggest beef with it, especially since the fix is so simple (literally 3 clicks on a Windows Forms or WPF app).</p>

<p>Please go to the following 3 Connect bugs <strong>and up-vote them</strong> so MS reconsiders fixing this.</p>

<ol>
  <li>
    <p><a href="https://connect.microsoft.com/SQLServer/feedback/details/755689/sql-server-management-studio-connect-to-server-popup-dialog" title="https://connect.microsoft.com/SQLServer/feedback/details/755689/sql-server-management-studio-connect-to-server-popup-dialog">https://connect.microsoft.com/SQLServer/feedback/details/755689/sql-server-management-studio-connect-to-server-popup-dialog</a></p>
  </li>
  <li>
    <p><a href="https://connect.microsoft.com/SQLServer/feedback/details/724909/connection-dialog-appears-off-screen" title="https://connect.microsoft.com/SQLServer/feedback/details/724909/connection-dialog-appears-off-screen">https://connect.microsoft.com/SQLServer/feedback/details/724909/connection-dialog-appears-off-screen</a></p>
  </li>
  <li>
    <p><a href="https://connect.microsoft.com/SQLServer/feedback/details/389165/sql-server-management-studio-gets-confused-dealing-with-multiple-displays" title="https://connect.microsoft.com/SQLServer/feedback/details/389165/sql-server-management-studio-gets-confused-dealing-with-multiple-displays">https://connect.microsoft.com/SQLServer/feedback/details/389165/sql-server-management-studio-gets-confused-dealing-with-multiple-displays</a></p>
  </li>
</ol>

<p>Here’s a screenshot of the problem. Here my secondary monitors are above my primary one, but the same problem occurs even if all monitors are horizontal to one another.</p>

<p><img src="/assets/Posts/2014/10/Sql-Management-Studio-Multi-Monitor-Bug.png" alt="Sql Management Studio Multi-Monitor Bug" /></p>]]></content><author><name>Daniel Schroeder</name></author><category term="SQL" /><category term="bug" /><category term="connect" /><category term="Connect to Server" /><category term="dialog" /><category term="Management" /><category term="monitor" /><category term="monitors" /><category term="multiple" /><category term="popup" /><category term="Server" /><category term="SQL" /><category term="Sql Server Management Studio" /><category term="SSMS" /><category term="Studio" /><category term="window" /><summary type="html"><![CDATA[If you use Sql Server Management Studio (SSMS) with multiple monitors, you likely run into the issue where the “Connect to Server” dialog window opens up either half or completely off the screen when SSMS is opened on a monitor that is not the primary one (see screenshot below).]]></summary></entry><entry><title type="html">Create Unique Strong Passwords That Are Easy To Remember For All Your Accounts, Without Using A Password Manager</title><link href="https://blog.danskingdom.com/create-unique-strong-passwords-that-are-easy-to-remember-for-all-your-accounts-without-using-a-password-manager/" rel="alternate" type="text/html" title="Create Unique Strong Passwords That Are Easy To Remember For All Your Accounts, Without Using A Password Manager" /><published>2014-10-11T21:38:51+00:00</published><updated>2014-10-11T21:38:51+00:00</updated><id>https://blog.danskingdom.com/create-unique-strong-passwords-that-are-easy-to-remember-for-all-your-accounts-without-using-a-password-manager</id><content type="html" xml:base="https://blog.danskingdom.com/create-unique-strong-passwords-that-are-easy-to-remember-for-all-your-accounts-without-using-a-password-manager/"><![CDATA[<h2 id="the-problem">The Problem</h2>

<p>We’ve all heard the warnings that we should use a strong password to prevent others from guessing our password, and that we should use a different password for every account we have.</p>

<p>A strong password is simply a password that meets a set of requirements, such as being at least X characters long and includes numbers and/or small letters and/or capital letters and/or symbols. Many websites and services enforce that a strong password be used.</p>

<p>If you don’t use a strong password, it’s likely that your password can be brute force hacked almost instantly. <a href="https://howsecureismypassword.net/">Check how secure your passwords are here</a>.</p>

<p>If you do use a strong password, it’s very likely that you use the same strong password (or set of strong passwords) for all of the services you use, simply because having to remember lots of passwords and which one is for which service is hard. This is very bad practice though, since if somebody gets your password they can access all of your services. There’s a lot of ways for somebody to get your password; from simply guessing it to software vulnerabilities like <a href="http://heartbleed.com/">the Heartbleed bug</a>, so you should try and always use a unique password for each service.</p>

<h2 id="the-solution">The Solution</h2>

<p>My super smart coworker Nathan Storms posted a very short blog about <a href="http://architectevangelist.wordpress.com/2014/09/29/smart-complex-passwords/">his solution to this problem</a>, which I’ll repeat and expand on here.</p>

<p>The basic idea is that instead of remembering a whole bunch of crazy passwords, <strong>you calculate them using an algorithm/formula</strong>. So instead of just using one password for all of your accounts, you use one formula to generate all of your passwords; That means instead of remembering a password, you just remember a formula. The formula can be as simple or complex as you like. Like most people, I prefer a simple one, but you don’t want it to be so simple that it’s easy for another person to guess it if they get ahold of one or two of your passwords.</p>

<p>The key to creating a unique password for each service that you use is to include part of the service’s name in your formula, such as the company name or website domain name.</p>

<p>The key to creating a strong password is to use a common strong phrase (or “salt” in security-speak) in all of your generated passwords.</p>

<p>The last piece to consider is that you want your salt + formula to generate a password that is not too short or too long. Longer passwords are always more secure, but many services have different min and max length requirements, so I find that aiming for about 12 characters satisfies most services while still generating a nice strong password.</p>

<h2 id="examples">Examples</h2>

<p>So the things we need are:</p>

<ol>
  <li>The service you are using. Let’s say you are creating an account at Google.com, so the service name is <strong>Google</strong>.</li>
  <li>A <strong>strong</strong> salt phrase. Let’s use: <strong>1Qaz!</strong> (notice it includes a number, small letter, capital letter, and symbol)</li>
</ol>

<h3 id="a-too-simple-formula-example">A Too Simple Formula Example</h3>

<p>A simple formula might be to simply combine the first 3 characters of the service name with our salt, so we get: <strong>Goo1Qaz!</strong></p>

<p>That’s not bad, but <a href="https://howsecureismypassword.net/" title="https://howsecureismypassword.net/">howsecureismypassword.net</a> tells us that it can be cracked within 3 days, which isn’t that great. We could simply change our salt to be a bit longer, such as 1Qaz!23&gt;, which would make our password <strong>Goo1Qaz!23&gt;</strong>. This puts our password at 11 characters and takes up to 50 thousand years to brute force, which is much better; Longer, stronger salts are always better.</p>

<p>There’s still a problem with this formula though; it’s too simple. To illustrate the point, for Yahoo.com the calculated password would be <strong>Yah1Qaz!23&gt;</strong>. Now, if somebody got ahold of these two passwords and knew which services they were for, how long do you think it would take them to figure out your formula and be able to calculate all of your passwords? Probably not very long at all.</p>

<h3 id="better-formula-examples">Better Formula Examples</h3>

<p>The problem with the formula above is that it’s easy for a human to recognize the pattern of how we use the service name; we just took the first 3 letters. Some better alternatives would be:</p>

<table cellspacing="0" cellpadding="2" width="699" border="0">
  <tr>
    <td valign="top" width="462">
      <p align="center">
        <strong>Service Name Rule (using Google) [using StackOverflow]</strong>
      </p>
    </td>

    <td valign="top" width="104">
      <p align="center">
        <strong>Google Password</strong>
      </p>
    </td>

    <td valign="top" width="131">
      <p align="center">
        <strong>StackOverflow Password</strong>
      </p>
    </td>
  </tr>

  <tr>
    <td valign="top" width="462">
      Use last 3 letters backwards (<strong>elg</strong>ooG) [<strong>wol</strong>frevOkcatS]
    </td>

    <td valign="top" width="104">
      <strong>elg</strong>1Qaz!23&gt;
    </td>

    <td valign="top" width="131">
      <strong>wol</strong>1Qaz!23&gt;
    </td>
  </tr>

  <tr>
    <td valign="top" width="462">
      Use every 2nd letter, max 4 letters (G<strong>o</strong>o<strong>g</strong>l<strong>e</strong>) [S<strong>t</strong>a<strong>c</strong>k<strong>O</strong>v<strong>e</strong>rflow]
    </td>

    <td valign="top" width="104">
      <strong>oge</strong>1Qaz!23&gt;
    </td>

    <td valign="top" width="131">
      <strong>tcOe</strong>1Qaz!23&gt;
    </td>
  </tr>

  <tr>
    <td valign="top" width="462">
      Use next letter of first 3 letters (G + 1 = <strong>H</strong>, o + 1 = <strong>p</strong>) [S + 1 = <strong>T</strong>, t + 1 = <strong>u</strong>, a + 1 + <strong>b</strong>]
    </td>

    <td valign="top" width="104">
      <strong>Hpp</strong>1Qaz!23&gt;
    </td>

    <td valign="top" width="131">
      <strong>Tub</strong>1Qaz!23&gt;
    </td>
  </tr>

  <tr>
    <td valign="top" width="462">
      Use number of vowels and total length (<strong>3</strong> vowels, length of <strong>6</strong>) [<strong>4</strong> vowels, length of <strong>13</strong>]
    </td>

    <td valign="top" width="104">
      <strong>36</strong>1Qaz!23&gt;
    </td>

    <td valign="top" width="131">
      <strong>413</strong>1Qaz!23&gt;
    </td>
  </tr>

  <tr>
    <td valign="top" width="462">
      Number of vowels in front, length at end
    </td>

    <td valign="top" width="104">
      <strong>3</strong>1Qaz!23&gt;<strong>6</strong>
    </td>

    <td valign="top" width="131">
      <strong>4</strong>1Qaz!23&gt;<strong>13</strong>
    </td>
  </tr>

  <tr>
    <td valign="top" width="462">
      Number of vowels in front, length minus number of vowels at end (<strong>3</strong> vowels, 6 – 3 = <strong>3</strong>) [<strong>4</strong> vowels, 13 – 4 = <strong>9</strong>]
    </td>

    <td valign="top" width="104">
      <strong>3</strong>1Qaz!23&gt;<strong>3</strong>
    </td>

    <td valign="top" width="131">
      <strong>4</strong>1Qaz!23&gt;<strong>9</strong>
    </td>
  </tr>

  <tr>
    <td valign="top" width="462">
      Number of vowels squared in front, length squared at end (3 * 3 = <strong>9</strong> and 6 * 6 = <strong>36</strong>) [4 * 4 = <strong>16</strong> and 13 * 13 = <strong>169</strong>]
    </td>

    <td valign="top" width="104">
      <strong>9</strong>1Qaz!23&gt;<strong>36</strong>
    </td>

    <td valign="top" width="131">
      <strong>16</strong>1Qaz!23&gt;<strong>169</strong>
    </td>
  </tr>
</table>

<p>You can see that once we introduce scrambling letters in the service name, or using numbers calculated from the service name, it becomes much harder for a human to spot the pattern and decode our formula. You want to be careful that your formula doesn’t get too complex for yourself though; StackOverflow is 13 characters long and I’ll admit that I broke out the calculator to see that 13 squared was 169.</p>

<p>You can also see how easy it is to come up with your own unique formula. You don’t have to stick to the rules I’ve shown here (counting vowels and length). Maybe instead of counting the number of vowels, you count the number of letters that the Service name has in common with your name. For example, my name is Daniel, so “Google” shares one letter in common with my name (the “l”), and “StackOverflow” shares 3 (“ael”). Maybe instead of squaring the numbers you multiply or add them. Maybe instead of using the numbers in your password, you use the symbols on the respective numbers. If you don’t like doing math, then avoid using math in your formula; it shouldn’t be a long or tedious process for you to calculate your password. Be creative and come up with your own formula that is fast and easy for you, and/or mix the components together in different ways.</p>

<h2 id="more-tips-and-considerations">More Tips and Considerations</h2>

<ul>
  <li>In all of my examples I placed my calculated characters before or after my salt, but you could also place them in the middle of your salt, or have your formula modify the salt.</li>
  <li>Since some services restrict the use of symbols, you may want to have another salt that does not contain symbols, or formula that does not generate symbols. When you try and login using your usual salt and it fails, try the password generated using your secondary symbol-free salt.</li>
  <li>For extra security, include the year in your formula somehow and change your passwords every year. If you are extra paranoid, or have to change your password very frequently (e.g. for work), you can do the same thing with the month too and change your passwords monthly. An alternative to this would be to change your salt phrase or formula every year/month.</li>
  <li>Similarly to how you may have had a different password for sites you don’t really care about, sites you do care about, and critical sites (e.g. bank websites), you could have different salts or formulas for each.</li>
  <li>If you are weary of using this formula approach for ALL of your passwords thinking that it is too much effort, then don’t use it for ALL of your passwords. Probably 85% of the accounts you create you don’t really care about; they don’t have any sensitive information, and you could really care less if somebody hacked them. For those, you can still use a shared strong password. Just use this approach for the remaining 15% of your accounts that you do really care about. This is a much better alternative than sharing a strong password among these 15%.</li>
  <li>Some characters are “stronger” than others. For example, symbols are typically harder to guess/crack than letters or numbers, and some symbols are stronger than other symbols (e.g. &lt; is stronger than $). It’s best to have a mix of all types of characters for your salt, but you might want to have more symbols in your salt, or when choosing the symbols for your salt you might opt for ones not on the 0 – 9 keys (i.e. &lt;!@#$%&gt;^&amp;*()).</li>
</ul>

<h2 id="why-not-just-use-a-password-manager">Why Not Just Use A Password Manager</h2>

<p>With a password manager you can easily have unique passwords for all of your accounts, but there are a few reasons why I like this formula approach over using password management software:</p>

<ol>
  <li>With password management software you are dependent on having the software installed and on hand; you can’t log into your accounts on your friend’s/co-worker’s/public PC since the password manager is not installed there. By using a formula instead, you ALWAYS know your passwords when you need them.</li>
  <li>Most password managers are not free, or else they are free on some platforms and not others, or they don’t support all of the platforms you use; if you want to use it on all of your devices you either can’t or you have to pay.</li>
  <li>Typically you need a password to access your account on the password manager. These types of “master passwords” are a bad idea. If somebody gets the “master password” for your password manager, they now have access to all of your passwords for all of your accounts. So even if you have a super strong master password that you never share with anybody, vulnerabilities like the Heartbleed bug make it possible for others to get your “master password”.</li>
  <li>Most password manager companies today store your passwords on their own servers in order to sync your passwords across all of your devices. This potentially makes them a large target for hackers, since if they can hack the company’s servers they get access to millions of passwords for millions of different services.</li>
</ol>

<h2 id="summary">Summary</h2>

<p>So instead of memorizing a password or set of passwords for all of the services you use, memorize a strong salt and a formula to calculate the passwords. Your formula doesn’t need to be overly complicated or involve a lot of hard math; just be creative with it and ensure that the formula is not obvious when looking at a few of the generated passwords. Also, you may want to have a couple different salts or formulas to help meet different strong password requirements on different services.</p>

<p>Happy password generating!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Security" /><category term="Security" /><category term="Account" /><category term="Credentials" /><category term="Password" /><category term="Strong" /><category term="Unique" /><summary type="html"><![CDATA[The Problem]]></summary></entry><entry><title type="html">Find Largest (Or Smallest) Files In A Directory Or Drive With PowerShell</title><link href="https://blog.danskingdom.com/find-largest-or-smallest-files-in-a-directory-or-drive-with-powershell/" rel="alternate" type="text/html" title="Find Largest (Or Smallest) Files In A Directory Or Drive With PowerShell" /><published>2014-09-08T22:38:38+00:00</published><updated>2014-09-08T22:38:38+00:00</updated><id>https://blog.danskingdom.com/find-largest-or-smallest-files-in-a-directory-or-drive-with-powershell</id><content type="html" xml:base="https://blog.danskingdom.com/find-largest-or-smallest-files-in-a-directory-or-drive-with-powershell/"><![CDATA[<p>One of our SQL servers was running low on disk space and I needed to quickly find the largest files on the drive to know what was eating up all of the disk space, so I wrote this PowerShell line that I thought I would share:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Get all files sorted by size.</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s1">'C:\SomeFolder'</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-File</span><span class="w"> </span><span class="o">|</span><span class="w">
  </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="nx">FullName</span><span class="p">,@{</span><span class="nx">Name</span><span class="o">=</span><span class="s1">'SizeGB'</span><span class="p">;</span><span class="nx">Expression</span><span class="o">=</span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1GB</span><span class="p">}},@{</span><span class="nx">Name</span><span class="o">=</span><span class="s1">'SizeMB'</span><span class="p">;</span><span class="nx">Expression</span><span class="o">=</span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1MB</span><span class="p">}},@{</span><span class="nx">Name</span><span class="o">=</span><span class="s1">'SizeKB'</span><span class="p">;</span><span class="nx">Expression</span><span class="o">=</span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1KB</span><span class="p">}}</span><span class="w"> </span><span class="err">|</span><span class="w">
  </span><span class="nx">Sort</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">SizeKB</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="err">-</span><span class="nx">Descending</span><span class="w"> </span><span class="err">|</span><span class="w">
  </span><span class="nx">Out</span><span class="err">-</span><span class="nx">GridView</span><span class="w">
</span></code></pre></div></div>

<p>If you are still only running PowerShell 2.0, it will complain that it doesn’t know what the -File switch is, so here’s the PowerShell 2.0 compatible version (which is a bit slower):</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Get all files sorted by size.</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s1">'C:\SomeFolder'</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w">
  </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">!</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
  </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="nx">FullName</span><span class="p">,@{</span><span class="nx">Name</span><span class="o">=</span><span class="s1">'SizeGB'</span><span class="p">;</span><span class="nx">Expression</span><span class="o">=</span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1GB</span><span class="p">}},@{</span><span class="nx">Name</span><span class="o">=</span><span class="s1">'SizeMB'</span><span class="p">;</span><span class="nx">Expression</span><span class="o">=</span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1MB</span><span class="p">}},@{</span><span class="nx">Name</span><span class="o">=</span><span class="s1">'SizeKB'</span><span class="p">;</span><span class="nx">Expression</span><span class="o">=</span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1KB</span><span class="p">}}</span><span class="w"> </span><span class="err">|</span><span class="w">
  </span><span class="nx">Sort</span><span class="err">-</span><span class="nx">Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">SizeKB</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="err">-</span><span class="nx">Descending</span><span class="w"> </span><span class="err">|</span><span class="w">
  </span><span class="nx">Out</span><span class="err">-</span><span class="nx">GridView</span><span class="w">
</span></code></pre></div></div>

<p>Just change ‘C:\SomeFolder’ to the folder/drive that you want scanned, and it will show you all of the files in the directory and subdirectories in a GridView sorted by size, along with their size in GB, MB, and KB. The nice thing about using a GridView is that it has built in filtering, so you can quickly do things like filter for certain file types, child directories, etc.</p>

<p>Here is a screenshot of the resulting GridView:</p>

<p><img src="/assets/Posts/2014/09/FilesSortedBySize.png" alt="Files Sorted By Size" /></p>

<p>And again with filtering applied (i.e. the .bak at the top to only show backup files):</p>

<p><img src="/assets/Posts/2014/09/FilesSortedBySizeAndFiltered.png" alt="Files Sorted By Size And Filtered" /></p>

<p>All done with PowerShell; no external tools required.</p>

<p>Happy Sys-Adminning!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Biggest" /><category term="file" /><category term="File System" /><category term="Largest" /><category term="PowerShell" /><category term="Size" /><category term="Smallest" /><summary type="html"><![CDATA[One of our SQL servers was running low on disk space and I needed to quickly find the largest files on the drive to know what was eating up all of the disk space, so I wrote this PowerShell line that I thought I would share:]]></summary></entry><entry><title type="html">Keep PowerShell Console Window Open After Script Finishes Running</title><link href="https://blog.danskingdom.com/keep-powershell-console-window-open-after-script-finishes-running/" rel="alternate" type="text/html" title="Keep PowerShell Console Window Open After Script Finishes Running" /><published>2014-07-07T21:57:14+00:00</published><updated>2014-07-07T21:57:14+00:00</updated><id>https://blog.danskingdom.com/keep-powershell-console-window-open-after-script-finishes-running</id><content type="html" xml:base="https://blog.danskingdom.com/keep-powershell-console-window-open-after-script-finishes-running/"><![CDATA[<p>I originally included this as a small bonus section at the end of <a href="https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces/">my other post about fixing the issue of not being able to run a PowerShell script whose path contains a space</a>, but thought this deserved its own dedicated post.</p>

<p>When running a script by double-clicking it, or by right-clicking it and choosing Run With PowerShell or Open With Windows PowerShell, if the script completes very quickly the user will see the PowerShell console appear very briefly and then disappear.  If the script gives output that the user wants to see, or if it throws an error, the user won’t have time to read the text.  We have 3 solutions to fix this so that the PowerShell console stays open after the script has finished running:</p>

<h2 id="1-one-time-solution">1. One-time solution</h2>

<p>Open a PowerShell console and manually run the script from the command line. I show how to do this a bit <a href="https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces/">in this post</a>, as the PowerShell syntax to run a script from the command-line is not straight-forward if you’ve never done it before.</p>

<p>The other way is to launch the PowerShell process from the Run box (Windows Key + R) or command prompt using the <strong>-NoExit</strong> switch and passing in the path to the PowerShell file.</p>

<p>For example: <strong>PowerShell -NoExit “C:\SomeFolder\MyPowerShellScript.ps1”</strong></p>

<h2 id="2-per-script-solution">2. Per-script solution</h2>

<p>Add a line like this to the end of your script:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Read-Host</span><span class="w"> </span><span class="nt">-Prompt</span><span class="w"> </span><span class="s2">"Press Enter to exit"</span><span class="w">
</span></code></pre></div></div>

<p>I typically use this following bit of code instead so that it only prompts for input when running from the PowerShell Console, and not from the PS ISE or other PS script editors (as they typically have a persistent console window integrated into the IDE).  Use whatever you prefer.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># If running in the console, wait for input before closing.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$Host</span><span class="o">.</span><span class="nf">Name</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"ConsoleHost"</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Press any key to continue..."</span><span class="w">
    </span><span class="bp">$Host</span><span class="o">.</span><span class="nf">UI</span><span class="o">.</span><span class="nf">RawUI</span><span class="o">.</span><span class="nf">ReadKey</span><span class="p">(</span><span class="s2">"NoEcho,IncludeKeyUp"</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>I typically use this approach for scripts that other people might end up running; if it’s a script that only I will ever be running, I rely on the global solution below.</p>

<h2 id="3-global-solution">3. Global solution</h2>

<p>Adjust the registry keys used to run a PowerShell script to include the –NoExit switch to prevent the console window from closing.  Here are the two registry keys we will target, along with their default value, and the value we want them to have:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Registry</span> <span class="n">Key</span><span class="p">:</span> <span class="n">HKEY_CLASSES_ROOT</span><span class="err">\</span><span class="n">Applications</span><span class="err">\</span><span class="n">powershell</span><span class="p">.</span><span class="n">exe</span><span class="err">\</span><span class="n">shell</span><span class="err">\</span><span class="n">open</span><span class="err">\</span><span class="n">command</span>
<span class="n">Description</span><span class="p">:</span> <span class="n">Key</span> <span class="n">used</span> <span class="n">when</span> <span class="n">you</span> <span class="n">right</span><span class="p">-</span><span class="n">click</span> <span class="n">a</span> <span class="p">.</span><span class="n">ps1</span> <span class="n">file</span> <span class="n">and</span> <span class="n">choose</span> <span class="n">Open</span> <span class="n">With</span> <span class="p">-&gt;</span> <span class="n">Windows</span> <span class="n">PowerShell</span><span class="p">.</span>
<span class="n">Default</span> <span class="n">Value</span><span class="p">:</span> <span class="s">"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"</span> <span class="s">"%1"</span>
<span class="n">Desired</span> <span class="n">Value</span><span class="p">:</span> <span class="s">"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"</span> <span class="s">"&amp; \"%1\""</span>

<span class="n">Registry</span> <span class="n">Key</span><span class="p">:</span> <span class="n">HKEY_CLASSES_ROOT</span><span class="err">\</span><span class="n">Microsoft</span><span class="p">.</span><span class="n">PowerShellScript</span><span class="p">.</span><span class="m">1</span><span class="err">\</span><span class="n">Shell</span><span class="err">\</span><span class="m">0</span><span class="err">\</span><span class="n">Command</span>
<span class="n">Description</span><span class="p">:</span> <span class="n">Key</span> <span class="n">used</span> <span class="n">when</span> <span class="n">you</span> <span class="n">right</span><span class="p">-</span><span class="n">click</span> <span class="n">a</span> <span class="p">.</span><span class="n">ps1</span> <span class="n">file</span> <span class="n">and</span> <span class="n">choose</span> <span class="n">Run</span> <span class="n">with</span> <span class="nf">PowerShell</span> <span class="p">(</span><span class="n">shows</span> <span class="n">up</span> <span class="n">depending</span> <span class="k">on</span> <span class="n">which</span> <span class="n">Windows</span> <span class="n">OS</span> <span class="n">and</span> <span class="n">Updates</span> <span class="n">you</span> <span class="n">have</span> <span class="n">installed</span><span class="p">).</span>
<span class="n">Default</span> <span class="n">Value</span><span class="p">:</span> <span class="s">"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"</span> <span class="s">"-Command"</span> <span class="s">"if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; &amp; '%1'"</span>
<span class="n">Desired</span> <span class="n">Value</span><span class="p">:</span> <span class="s">"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"</span> <span class="p">-</span><span class="n">NoExit</span> <span class="s">"-Command"</span> <span class="s">"if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; &amp; \"%1\""</span>
</code></pre></div></div>

<p>The Desired Values add the –NoExit switch, as well wrap the %1 in double quotes to <a href="https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces/">allow the script to still run even if it’s path contains spaces</a>.</p>

<p>If you want to open the registry and manually make the change you can, or here is the registry script that we can run to make the change automatically for us:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Windows</span> <span class="n">Registry</span> <span class="n">Editor</span> <span class="n">Version</span> <span class="m">5.00</span>

<span class="p">[</span><span class="n">HKEY_CLASSES_ROOT</span><span class="err">\</span><span class="n">Applications</span><span class="err">\</span><span class="n">powershell</span><span class="p">.</span><span class="n">exe</span><span class="err">\</span><span class="n">shell</span><span class="err">\</span><span class="n">open</span><span class="err">\</span><span class="n">command</span><span class="p">]</span>
<span class="err">@</span><span class="p">=</span><span class="s">"\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"&amp; \\\"%1\\\"\""</span>

<span class="p">[</span><span class="n">HKEY_CLASSES_ROOT</span><span class="err">\</span><span class="n">Microsoft</span><span class="p">.</span><span class="n">PowerShellScript</span><span class="p">.</span><span class="m">1</span><span class="err">\</span><span class="n">Shell</span><span class="p">&amp;</span><span class="err">#</span><span class="m">92</span><span class="p">;&amp;</span><span class="err">#</span><span class="m">48</span><span class="p">;</span><span class="err">\</span><span class="n">Command</span><span class="p">]</span>
<span class="err">@</span><span class="p">=</span><span class="s">"\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"-Command\" \"if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; &amp; \\\"%1\\\"\""</span>
</code></pre></div></div>

<p>You can copy and paste the text into a file with a .reg extension, or just <a href="/assets/Posts/2014/07/FixRunPowerShellScriptWithSpacesInPathProblemAndLeaveConsoleOpenWhenScriptCompletes.zip">download it here</a>.</p>

<p>Simply double-click the .reg file and click OK on the prompt to have the registry keys updated.  Now by default when you run a PowerShell script from File Explorer (i.e. Windows Explorer), the console window will stay open even after the script is finished executing.  From there you can just type <strong>exit</strong> and hit enter to close the window, or use the mouse to click the window’s X in the top right corner.</p>

<p>If I have missed other common registry keys or any other information, please leave a comment to let me know.  I hope you find this useful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Close" /><category term="Console" /><category term="Explorer" /><category term="File Explorer" /><category term="Finish" /><category term="Open" /><category term="PowerShell" /><category term="Windows Explorer" /><summary type="html"><![CDATA[I originally included this as a small bonus section at the end of my other post about fixing the issue of not being able to run a PowerShell script whose path contains a space, but thought this deserved its own dedicated post.]]></summary></entry><entry><title type="html">Browser Extensions To Expand GitHub Code Pages To Fill The Full Width Of Your Browser</title><link href="https://blog.danskingdom.com/browser-extensions-to-expand-github-code-pages-to-fill-the-full-width-of-your-browser/" rel="alternate" type="text/html" title="Browser Extensions To Expand GitHub Code Pages To Fill The Full Width Of Your Browser" /><published>2014-05-27T20:26:46+00:00</published><updated>2014-05-27T20:26:46+00:00</updated><id>https://blog.danskingdom.com/browser-extensions-to-expand-github-code-pages-to-fill-the-full-width-of-your-browser</id><content type="html" xml:base="https://blog.danskingdom.com/browser-extensions-to-expand-github-code-pages-to-fill-the-full-width-of-your-browser/"><![CDATA[<h2 id="the-problem">The problem</h2>

<p>I love GitHub, but one thing that I and most developers hate is that the pages that show source code (Pull requests, commits, blobs) are locked to a fixed width, and it’s only about 900 pixels. Most developers have widescreen monitors, so their code lines are typically longer than 900 pixels. This can make viewing code on GitHub painful because you have to constantly horizontally scroll to see a whole line of code. I honestly can’t believe that after years GitHub still hasn’t fixed this. It either means that the GitHub developers don’t dogfood their own product, or the website designers (not programmers) have the final say on how the site looks, in which case they don’t know their target audience very well. Anyways, I digress.</p>

<h2 id="my-solution">My solution</h2>

<p>To solve this problem I wrote a GreaseMonkey user script 2 years ago that expands the code section on GitHub to fill the width of your browser, and it works great. The problem was that <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">GreaseMonkey is a FireFox-only extension</a>. Luckily, these days most browsers have a GreaseMonkey equivalent:</p>

<p>Internet Explorer has one called <a href="http://www.pcworld.com/product/952510/trixie.html">Trixie</a>.</p>

<p>Chrome has one called <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo">TamperMonkey</a>. Chrome also supports user scripts natively so you can <a href="http://stackoverflow.com/a/13672143/602585">install them without TamperMonkey</a>, but TamperMonkey helps with the install/uninstall/managing of them.</p>

<p>So if you have GreaseMonkey or an equivalent installed, then you can simply go ahead and <a href="https://greasyfork.org/scripts/1711-make-github-pull-request-commit-and-blob-pages-full-width">install my user script for free</a> and start viewing code on GitHub in widescreen glory.</p>

<p>Alternatively, I have also released a free Chrome extension in the Chrome Web Store called <a href="https://chrome.google.com/webstore/detail/make-github-pages-full-wi/dfpgjcidmobcpaoolhgchdcmdgenbaoa">Make GitHub Pages Full Width</a>. When you install it from the store you get all of the added Store benefits, such as having the extension sync across all of your PCs, automatically getting it installed again after you format your PC, etc.</p>

<h2 id="results">Results</h2>

<p>If you install the extension and a code page doesn’t expand it’s width to fit your page, just refresh the page. If anybody knows how to fix this issue please let me know.</p>

<p>And to give you an idea of what the result looks like, here are 2 screenshots; one without the extension installed (top, notice some text goes out of view), and one with it (bottom).</p>

<p><img src="/assets/Posts/2014/05/WithoutFullWidth.png" alt="Without Full Width" /></p>

<p><img src="/assets/Posts/2014/05/WithFullWidth.png" alt="With Full Width" /></p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Browser" /><category term="Productivity" /><category term="Browser" /><category term="Code" /><category term="Extension" /><category term="Fill" /><category term="Full" /><category term="GitHub" /><category term="Review" /><category term="View" /><category term="Width" /><summary type="html"><![CDATA[The problem]]></summary></entry><entry><title type="html">Adding a WPF Settings Page To The Tools Options Dialog Window For Your Visual Studio Extension</title><link href="https://blog.danskingdom.com/adding-a-wpf-settings-page-to-the-tools-options-dialog-window-for-your-visual-studio-extension/" rel="alternate" type="text/html" title="Adding a WPF Settings Page To The Tools Options Dialog Window For Your Visual Studio Extension" /><published>2014-04-25T19:14:44+00:00</published><updated>2014-04-25T19:14:44+00:00</updated><id>https://blog.danskingdom.com/adding-a-wpf-settings-page-to-the-tools-options-dialog-window-for-your-visual-studio-extension</id><content type="html" xml:base="https://blog.danskingdom.com/adding-a-wpf-settings-page-to-the-tools-options-dialog-window-for-your-visual-studio-extension/"><![CDATA[<p>I recently created my first Visual Studio extension, <a href="http://visualstudiogallery.msdn.microsoft.com/d8d61cc9-6660-41af-b8d0-0f8403b4b39c">Diff All Files</a>, which allows you to quickly compare the changes to all files in a TFS changeset, shelveset, or pending changes (Git support coming soon). One of the first challenges I faced when I started the project was where to display my extension’s settings to the user, and where to save them. My first instinct was to create a new Menu item to launch a page with all of the settings to display, since the wizard you go through to create the project has an option to automatically add a new Menu item the Tools menu. After some Googling though, I found the more acceptable solution is to create a new section within the Tools -&gt; Options window for your extension, as this will also allow the user to <a href="http://msdn.microsoft.com/en-us/library/bb166176.aspx">import and export your extension’s settings</a>.</p>

<h2 id="adding-a-grid-or-custom-windows-forms-settings-page">Adding a grid or custom Windows Forms settings page</h2>

<p>Luckily I found this <a href="http://stackoverflow.com/a/6247183/602585">Stack Overflow answer that shows a Visual Basic example of how to do this</a>, and links to <a href="http://msdn.microsoft.com/en-us/library/bb166195.aspx">the MSDN page that also shows how to do this in C#</a>. The MSDN page is a great resource, and it shows you everything you need to create your settings page as either a Grid Page, or a Custom Page using Windows Forms (FYI: when it says to add a UserControl, it means a System.Windows.Forms.UserControl, not a System.Windows.Controls.UserControl). My extension’s settings page needed to have buttons on it to perform some operations, which is something the Grid Page doesn’t support, so I had to make a Custom Page. I first made it using Windows Forms as the page shows, but it quickly reminded me how out-dated Windows Forms is (no binding!), and my settings page would have to be a fixed width and height, rather than expanding to the size of the users Options dialog window, which I didn’t like.</p>

<h2 id="adding-a-custom-wpf-settings-page">Adding a custom WPF settings page</h2>

<p>The steps to create a Custom WPF settings page are the same as for <a href="http://msdn.microsoft.com/en-us/library/bb166195.aspx">creating a Custom Windows Forms Page</a>, except instead having your settings control inherit from System.Forms.DialogPage (steps 1 and 2 on that page), it needs to inherit from <strong>Microsoft.VisualStudio.Shell.UIElementDialogPage</strong>. And when you create your User Control for the settings page’s UI, it will be a WPF System.Windows.Controls.UserControl. Also, instead of overriding the Window method of the DialogPage class, you will override the Child method of the UIElementDialogPage class.</p>

<p>Here’s a sample of what the Settings class might look like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.ComponentModel</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Linq</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Runtime.InteropServices</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Microsoft.VisualStudio.Shell</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">VS_DiffAllFiles.Settings</span>
<span class="p">{</span>
    <span class="p">[</span><span class="nf">ClassInterface</span><span class="p">(</span><span class="n">ClassInterfaceType</span><span class="p">.</span><span class="n">AutoDual</span><span class="p">)]</span>
    <span class="p">[</span><span class="nf">Guid</span><span class="p">(</span><span class="s">"1D9ECCF3-5D2F-4112-9B25-264596873DC9"</span><span class="p">)]</span>  <span class="c1">// Special guid to tell it that this is a custom Options dialog page, not the built-in grid dialog page.</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">DiffAllFilesSettings</span> <span class="p">:</span> <span class="n">UIElementDialogPage</span><span class="p">,</span> <span class="n">INotifyPropertyChanged</span>
    <span class="p">{</span>
        <span class="err">#</span><span class="n">region</span> <span class="n">Notify</span> <span class="n">Property</span> <span class="n">Changed</span>
        <span class="c1">/// &lt;summary&gt;</span>
        <span class="c1">/// Inherited event from INotifyPropertyChanged.</span>
        <span class="c1">/// &lt;/summary&gt;</span>
        <span class="k">public</span> <span class="k">event</span> <span class="n">PropertyChangedEventHandler</span> <span class="n">PropertyChanged</span><span class="p">;</span>

        <span class="c1">/// &lt;summary&gt;</span>
        <span class="c1">/// Fires the PropertyChanged event of INotifyPropertyChanged with the given property name.</span>
        <span class="c1">/// &lt;/summary&gt;</span>
        <span class="c1">/// &lt;param name="propertyName"&gt;The name of the property to fire the event against&lt;/param&gt;</span>
        <span class="k">public</span> <span class="k">void</span> <span class="nf">NotifyPropertyChanged</span><span class="p">(</span><span class="kt">string</span> <span class="n">propertyName</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">PropertyChanged</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
                <span class="nf">PropertyChanged</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="k">new</span> <span class="nf">PropertyChangedEventArgs</span><span class="p">(</span><span class="n">propertyName</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="err">#</span><span class="n">endregion</span>

        <span class="c1">/// &lt;summary&gt;</span>
        <span class="c1">/// Get / Set if new files being added to source control should be compared.</span>
        <span class="c1">/// &lt;/summary&gt;</span>
        <span class="k">public</span> <span class="kt">bool</span> <span class="n">CompareNewFiles</span> <span class="p">{</span> <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="n">_compareNewFiles</span><span class="p">;</span> <span class="p">}</span> <span class="k">set</span> <span class="p">{</span> <span class="n">_compareNewFiles</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="nf">NotifyPropertyChanged</span><span class="p">(</span><span class="s">"CompareNewFiles"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span>
        <span class="k">private</span> <span class="kt">bool</span> <span class="n">_compareNewFiles</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>

        <span class="err">#</span><span class="n">region</span> <span class="n">Overridden</span> <span class="n">Functions</span>

        <span class="c1">/// &lt;summary&gt;</span>
        <span class="c1">/// Gets the Windows Presentation Foundation (WPF) child element to be hosted inside the Options dialog page.</span>
        <span class="c1">/// &lt;/summary&gt;</span>
        <span class="c1">/// &lt;returns&gt;The WPF child element.&lt;/returns&gt;</span>
        <span class="k">protected</span> <span class="k">override</span> <span class="n">System</span><span class="p">.</span><span class="n">Windows</span><span class="p">.</span><span class="n">UIElement</span> <span class="n">Child</span>
        <span class="p">{</span>
            <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">DiffAllFilesSettingsPageControl</span><span class="p">(</span><span class="k">this</span><span class="p">);</span> <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">/// &lt;summary&gt;</span>
        <span class="c1">/// Should be overridden to reset settings to their default values.</span>
        <span class="c1">/// &lt;/summary&gt;</span>
        <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">ResetSettings</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="n">CompareNewFiles</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
            <span class="k">base</span><span class="p">.</span><span class="nf">ResetSettings</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="err">#</span><span class="n">endregion</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And what the code-behind for the User Control might look like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Diagnostics</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Linq</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Windows</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Windows.Controls</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Windows.Input</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Windows.Navigation</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">VS_DiffAllFiles.Settings</span>
<span class="p">{</span>
    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Interaction logic for DiffAllFilesSettingsPageControl.xaml</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">DiffAllFilesSettingsPageControl</span> <span class="p">:</span> <span class="n">UserControl</span>
    <span class="p">{</span>
        <span class="c1">/// &lt;summary&gt;</span>
        <span class="c1">/// A handle to the Settings instance that this control is bound to.</span>
        <span class="c1">/// &lt;/summary&gt;</span>
        <span class="k">private</span> <span class="n">DiffAllFilesSettings</span> <span class="n">_settings</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>

        <span class="k">public</span> <span class="nf">DiffAllFilesSettingsPageControl</span><span class="p">(</span><span class="n">DiffAllFilesSettings</span> <span class="n">settings</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="nf">InitializeComponent</span><span class="p">();</span>
            <span class="n">_settings</span> <span class="p">=</span> <span class="n">settings</span><span class="p">;</span>
            <span class="k">this</span><span class="p">.</span><span class="n">DataContext</span> <span class="p">=</span> <span class="n">_settings</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">private</span> <span class="k">void</span> <span class="nf">btnRestoreDefaultSettings_Click</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">RoutedEventArgs</span> <span class="n">e</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">_settings</span><span class="p">.</span><span class="nf">ResetSettings</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="k">private</span> <span class="k">void</span> <span class="nf">UserControl_LostKeyboardFocus</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">KeyboardFocusChangedEventArgs</span> <span class="n">e</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="c1">// Find all TextBoxes in this control force the Text bindings to fire to make sure all changes have been saved.</span>
            <span class="c1">// This is required because if the user changes some text, then clicks on the Options Window's OK button, it closes</span>
            <span class="c1">// the window before the TextBox's Text bindings fire, so the new value will not be saved.</span>
            <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">textBox</span> <span class="k">in</span> <span class="n">DiffAllFilesHelper</span><span class="p">.</span><span class="n">FindVisualChildren</span><span class="p">&lt;</span><span class="n">TextBox</span><span class="p">&gt;(</span><span class="n">sender</span> <span class="k">as</span> <span class="n">UserControl</span><span class="p">))</span>
            <span class="p">{</span>
                <span class="kt">var</span> <span class="n">bindingExpression</span> <span class="p">=</span> <span class="n">textBox</span><span class="p">.</span><span class="nf">GetBindingExpression</span><span class="p">(</span><span class="n">TextBox</span><span class="p">.</span><span class="n">TextProperty</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">bindingExpression</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="n">bindingExpression</span><span class="p">.</span><span class="nf">UpdateSource</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here’s the corresponding xaml for the UserControl:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;UserControl</span> <span class="na">x:Class=</span><span class="s">"VS_DiffAllFiles.Settings.DiffAllFilesSettingsPageControl"</span>
                         <span class="na">xmlns=</span><span class="s">"http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
                         <span class="na">xmlns:x=</span><span class="s">"http://schemas.microsoft.com/winfx/2006/xaml"</span>
                         <span class="na">xmlns:mc=</span><span class="s">"http://schemas.openxmlformats.org/markup-compatibility/2006"</span>
                         <span class="na">xmlns:d=</span><span class="s">"http://schemas.microsoft.com/expression/blend/2008"</span>
                         <span class="na">xmlns:xctk=</span><span class="s">"http://schemas.xceed.com/wpf/xaml/toolkit"</span>
                         <span class="na">xmlns:QC=</span><span class="s">"clr-namespace:QuickConverter;assembly=QuickConverter"</span>
                         <span class="na">mc:Ignorable=</span><span class="s">"d"</span>
                         <span class="na">d:DesignHeight=</span><span class="s">"350"</span> <span class="na">d:DesignWidth=</span><span class="s">"400"</span> <span class="na">LostKeyboardFocus=</span><span class="s">"UserControl_LostKeyboardFocus"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;UserControl.Resources&gt;</span>
    <span class="nt">&lt;/UserControl.Resources&gt;</span>

    <span class="nt">&lt;Grid&gt;</span>
        <span class="nt">&lt;StackPanel</span> <span class="na">Orientation=</span><span class="s">"Vertical"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;CheckBox</span> <span class="na">Content=</span><span class="s">"Compare new files"</span> <span class="na">IsChecked=</span><span class="s">"{Binding Path=CompareNewFiles}"</span> <span class="na">ToolTip=</span><span class="s">"If files being added to source control should be compared."</span> <span class="nt">/&gt;</span>
            <span class="nt">&lt;Button</span> <span class="na">Content=</span><span class="s">"Restore Default Settings"</span> <span class="na">Click=</span><span class="s">"btnRestoreDefaultSettings_Click"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;/StackPanel&gt;</span>
    <span class="nt">&lt;/Grid&gt;</span>
<span class="nt">&lt;/UserControl&gt;</span>
</code></pre></div></div>

<p>You can see that I am binding the CheckBox directly to the CompareNewFiles property on the instance of my Settings class; yay, no messing around with Checked events :-).</p>

<p>This is a complete, but very simple example. If you want a more detailed example that shows more controls, check out <a href="https://diffallfilesvisualstudioextension.codeplex.com/SourceControl/latest#VS.DiffAllFiles/Settings/DiffAllFilesSettings.cs">the source code for my Diff All Files extension</a>.</p>

<h2 id="a-minor-problem">A minor problem</h2>

<p>One problem I found was that when using a TextBox on my Settings Page UserControl, if I edited text in a TextBox and then hit the OK button on the Options dialog to close the window, the new text would not actually get applied. This was because the window would get closed before the TextBox bindings had a chance to fire; so if I instead clicked out of the TextBox before clicking the OK button, everything worked correctly. I know you can change the binding’s UpdateSourceTrigger to PropertyChanged, but I perform some additional logic when some of my textbox text is changed, and I didn’t want that logic firing after every key press while the user typed in the TextBox.</p>

<p>To solve this problem I added a LostKeyboardFocus event to the UserControl, and in that event I find all TextBox controls on the UserControl and force their bindings to update. You can see the code for this in the snippets above. The one piece of code that’s not shown is the FindVisualChildren<TextBox> method, so here it is:</TextBox></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Recursively finds the visual children of the given control.</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="c1">/// &lt;typeparam name="T"&gt;The type of control to look for.&lt;/typeparam&gt;</span>
<span class="c1">/// &lt;param name="dependencyObject"&gt;The dependency object.&lt;/param&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">FindVisualChildren</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">DependencyObject</span> <span class="n">dependencyObject</span><span class="p">)</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="n">DependencyObject</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">dependencyObject</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">VisualTreeHelper</span><span class="p">.</span><span class="nf">GetChildrenCount</span><span class="p">(</span><span class="n">dependencyObject</span><span class="p">);</span> <span class="n">index</span><span class="p">++)</span>
        <span class="p">{</span>
            <span class="n">DependencyObject</span> <span class="n">child</span> <span class="p">=</span> <span class="n">VisualTreeHelper</span><span class="p">.</span><span class="nf">GetChild</span><span class="p">(</span><span class="n">dependencyObject</span><span class="p">,</span> <span class="n">index</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">child</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&amp;</span><span class="n">amp</span><span class="p">;&amp;</span><span class="n">amp</span><span class="p">;</span> <span class="n">child</span> <span class="k">is</span> <span class="n">T</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="k">yield</span> <span class="k">return</span> <span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="n">child</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="k">foreach</span> <span class="p">(</span><span class="n">T</span> <span class="n">childOfChild</span> <span class="k">in</span> <span class="n">FindVisualChildren</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">child</span><span class="p">))</span>
            <span class="p">{</span>
                <span class="k">yield</span> <span class="k">return</span> <span class="n">childOfChild</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And that’s it. Now you know how to make a nice Settings Page for your Visual Studio extension using WPF, instead of the archaic Windows Forms.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="Custom" /><category term="Custom Page" /><category term="Options" /><category term="Settings" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="Windows Forms" /><category term="WPF" /><summary type="html"><![CDATA[I recently created my first Visual Studio extension, Diff All Files, which allows you to quickly compare the changes to all files in a TFS changeset, shelveset, or pending changes (Git support coming soon). One of the first challenges I faced when I started the project was where to display my extension’s settings to the user, and where to save them. My first instinct was to create a new Menu item to launch a page with all of the settings to display, since the wizard you go through to create the project has an option to automatically add a new Menu item the Tools menu. After some Googling though, I found the more acceptable solution is to create a new section within the Tools -&gt; Options window for your extension, as this will also allow the user to import and export your extension’s settings.]]></summary></entry><entry><title type="html">Template Solution For Deploying TFS Checkin Policies To Multiple Versions Of Visual Studio And Having Them Automatically Work From “TF.exe Checkin” Too</title><link href="https://blog.danskingdom.com/template-solution-for-deploying-tfs-checkin-policies-to-multiple-versions-of-visual-studio-and-having-them-automatically-work-from-tf-exe-checkin-too/" rel="alternate" type="text/html" title="Template Solution For Deploying TFS Checkin Policies To Multiple Versions Of Visual Studio And Having Them Automatically Work From “TF.exe Checkin” Too" /><published>2014-03-24T16:55:05+00:00</published><updated>2014-03-24T16:55:05+00:00</updated><id>https://blog.danskingdom.com/template-solution-for-deploying-tfs-checkin-policies-to-multiple-versions-of-visual-studio-and-having-them-automatically-work-from-tf-exe-checkin-too</id><content type="html" xml:base="https://blog.danskingdom.com/template-solution-for-deploying-tfs-checkin-policies-to-multiple-versions-of-visual-studio-and-having-them-automatically-work-from-tf-exe-checkin-too/"><![CDATA[<h2 id="get-the-source-code">Get the source code</h2>

<p>Let’s get right to it by giving you the source code. You can <a href="http://code.msdn.microsoft.com/windowsdesktop/Deploying-Checkin-Policies-d306493a">get it from the MSDN samples here</a>.</p>

<h2 id="explanation-of-source-code-and-adding-new-checkin-policies">Explanation of source code and adding new checkin policies</h2>

<p>If you open the Visual Studio (VS) solution the first thing you will likely notice is that there are 5 projects. CheckinPolicies.VS2012 simply references all of the files in CheckinPolicies.VS2013 as links (i.e. shortcut files); this is because we need to compile the CheckinPolicies.VS2012 project using TFS 2012 assemblies, and the CheckinPolicies.VS2013 project using TFS2013 assemblies, but want both projects to have all of the same checkin policies. So the projects contain all of the same files; just a few of their references are different. A copy of the references that are different between the two projects are stored in the project’s “Dependencies” folder; these are the Team Foundation assemblies that are specific to VS 2012 and 2013. Having these assemblies stored in the solution allows us to still build the VS 2012 checkin policies, even if you (or a colleague) only has VS 2013 installed.</p>

<p><strong>Update:</strong> To avoid having multiple CheckinPolicy.VS* projects, we could use <a href="http://blogs.msdn.com/b/phkelley/archive/2013/08/12/checkin-policy-multitargeting.aspx">the msbuild targets technique that P. Kelly shows on his blog</a>. However, I believe we would still need multiple deployment projects, as described below, in order to have the checkin policies work outside of Visual Studio.</p>

<p>The other projects are CheckinPolicyDeployment.VS2012 and CheckinPolicyDeployment.VS2013 (both of which are VSPackage projects), and CheckinPolicyDeploymentShared. The CheckinPolicyDeployment.VS2012/VS2013 projects will generate the VSIX files that are used to distribute the checkin policies, and CheckinPolicyDeploymentShared contains files/code that are common to both of the projects (the projects reference the files by linking to them).</p>

<p>Basically everything is ready to go. Just start adding new checkin policy classes to the CheckinPolicy.VS2013 project, and then also add them to the CheckinPolicy.VS2012 project as a link. You can add a file as a link in 2 different ways in the Solution Explorer:</p>

<ol>
  <li>Right-click on the CheckinPolicies.VS2012 project and choose <strong>Add -&gt; Existing Item…</strong>, and then navigate to the new class file that you added to the CheckinPolicy.VS2013 project. Instead of clicking the Add button though, click the little down arrow on the side of the Add button and then choose <strong>Add As Link</strong>.</li>
  <li>Drag and drop the file from the CheckinPolicy.VS2013 project to the CheckinPolicy.VS2012 project, but while releasing the left mouse button to drop the file, hold down the <strong>Alt</strong> key; this will change the operation from adding a copy of the file to that project, to adding a shortcut file that links back to the original file.</li>
</ol>

<p>There is a <strong>DummyCheckinPolicy.cs</strong> file in the CheckinPolicies.VS2013 project that shows you an example of how to create a new checkin policy. Basically you just need to create a new public, serializable class that extends the CheckinPolicyBase class. The actual logic for your checkin policy to perform goes in the Evaluate() function. If there is a policy violation in the code that is trying to be checked in, just add a new PolicyFailure instance to the <strong>failures</strong> list with the message that you want the user to see.</p>

<h2 id="building-a-new-version-of-your-checkin-policies">Building a new version of your checkin policies</h2>

<p>Once you are ready to deploy your policies, you will want to update the version number in the <strong>source.extension.vsixmanifest</strong> file in both the CheckinPolicyDeployment.VS2012 and CheckinPolicyDeployment.VS2013 projects. Since these projects will both contain the same policies, I recommend giving them the same version number as well. Once you have updated the version number, build the solution in Release mode. From there you will find the new VSIX files at “CheckinPolicyDeployment.VS2012\bin\Release\TFS Checkin Policies VS2012.vsix” and “CheckinPolicyDeployment.VS2013\bin\Release\TFS Checkin Policies VS2013.vsix”. You can then distribute them to your team; I recommend <a href="http://blogs.msdn.com/b/visualstudio/archive/2011/10/03/private-extension-galleries-for-the-enterprise.aspx">setting up an internal VS Extension Gallery</a>, but the poor-man’s solution is to just email the vsix file out to everyone on your team.</p>

<h2 id="having-the-policies-automatically-work-outside-of-visual-studio">Having the policies automatically work outside of Visual Studio</h2>

<p>This is already hooked up and working in the template solution, so nothing needs to be changed there, but I will explain how it works here. A while back I blogged about <a href="https://blog.danskingdom.com/getting-custom-tfs-checkin-policies-to-work-when-committing-from-the-command-line-i-e-tf-checkin/">how to get your Team Foundation Server (TFS) checkin polices to still work when checking code in from the command line</a> via the “tf checkin” command; by default when installing your checkin policies via a VSIX package (the MS recommended approach) you can only get them to work in Visual Studio. I hated that I would need to manually run the script I provided each time the checkin policies were updated, so I posted <a href="http://stackoverflow.com/questions/18647866/run-script-during-after-vsix-install">a question on Stack Overflow about how to run a script automatically after the VSIX package installs the extension</a>. So it turns out that you can’t do that, but what you can do is use a VSPackage instead, which still uses VSIX to deploy the extension, but then also allows us to hook into Visual Studio events to run our script when VS starts up or exits.</p>

<p>Here is the VSPackage class code to hook up the events and call our UpdateCheckinPoliciesInRegistry() function:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// This is the class that implements the package exposed by this assembly.</span>
<span class="c1">///</span>
<span class="c1">/// The minimum requirement for a class to be considered a valid package for Visual Studio</span>
<span class="c1">/// is to implement the IVsPackage interface and register itself with the shell.</span>
<span class="c1">/// This package uses the helper classes defined inside the Managed Package Framework (MPF)</span>
<span class="c1">/// to do it: it derives from the Package class that provides the implementation of the</span>
<span class="c1">/// IVsPackage interface and uses the registration attributes defined in the framework to</span>
<span class="c1">/// register itself and its components with the shell.</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="c1">// This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is</span>
<span class="c1">// a package.</span>
<span class="p">[</span><span class="nf">PackageRegistration</span><span class="p">(</span><span class="n">UseManagedResourcesOnly</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
<span class="c1">// This attribute is used to register the information needed to show this package</span>
<span class="c1">// in the Help/About dialog of Visual Studio.</span>
<span class="p">[</span><span class="nf">InstalledProductRegistration</span><span class="p">(</span><span class="s">"#110"</span><span class="p">,</span> <span class="s">"#112"</span><span class="p">,</span> <span class="s">"1.0"</span><span class="p">,</span> <span class="n">IconResourceID</span> <span class="p">=</span> <span class="m">400</span><span class="p">)]</span>
<span class="c1">// Auto Load our assembly even when no solution is open (by using the Microsoft.VisualStudio.VSConstants.UICONTEXT_NoSolution guid).</span>
<span class="p">[</span><span class="nf">ProvideAutoLoad</span><span class="p">(</span><span class="s">"ADFC4E64-0397-11D1-9F4E-00A0C911004F"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">CheckinPolicyDeploymentPackage</span> <span class="p">:</span> <span class="n">Package</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="n">EnvDTE</span><span class="p">.</span><span class="n">DTEEvents</span> <span class="n">_dteEvents</span><span class="p">;</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Initialization of the package; this method is called right after the package is sited, so this is the place</span>
    <span class="c1">/// where you can put all the initialization code that rely on services provided by VisualStudio.</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Initialize</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">base</span><span class="p">.</span><span class="nf">Initialize</span><span class="p">();</span>

        <span class="kt">var</span> <span class="n">dte</span> <span class="p">=</span> <span class="p">(</span><span class="n">DTE2</span><span class="p">)</span><span class="nf">GetService</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">SDTE</span><span class="p">));</span>
        <span class="n">_dteEvents</span> <span class="p">=</span> <span class="n">dte</span><span class="p">.</span><span class="n">Events</span><span class="p">.</span><span class="n">DTEEvents</span><span class="p">;</span>
        <span class="n">_dteEvents</span><span class="p">.</span><span class="n">OnBeginShutdown</span> <span class="p">+=</span> <span class="n">OnBeginShutdown</span><span class="p">;</span>

        <span class="nf">UpdateCheckinPoliciesInRegistry</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">OnBeginShutdown</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">_dteEvents</span><span class="p">.</span><span class="n">OnBeginShutdown</span> <span class="p">-=</span> <span class="n">OnBeginShutdown</span><span class="p">;</span>
        <span class="n">_dteEvents</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>

        <span class="nf">UpdateCheckinPoliciesInRegistry</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">void</span> <span class="nf">UpdateCheckinPoliciesInRegistry</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">dte</span> <span class="p">=</span> <span class="p">(</span><span class="n">DTE2</span><span class="p">)</span><span class="nf">GetService</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">SDTE</span><span class="p">));</span>
        <span class="kt">string</span> <span class="n">visualStudioVersionNumber</span> <span class="p">=</span> <span class="n">dte</span><span class="p">.</span><span class="n">Version</span><span class="p">;</span>
        <span class="kt">string</span> <span class="n">customCheckinPolicyEntryName</span> <span class="p">=</span> <span class="s">"CheckinPolicies"</span><span class="p">;</span>

        <span class="c1">// Create the paths to the registry keys that contains the values to inspect.</span>
        <span class="kt">string</span> <span class="n">desiredRegistryKeyPath</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"HKEY_CURRENT_USER\\Software\\Microsoft\\VisualStudio\\{0}_Config\\TeamFoundation\\SourceControl\\Checkin Policies"</span><span class="p">,</span> <span class="n">visualStudioVersionNumber</span><span class="p">);</span>
        <span class="kt">string</span> <span class="n">currentRegistryKeyPath</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">Environment</span><span class="p">.</span><span class="n">Is64BitOperatingSystem</span><span class="p">)</span>
            <span class="n">currentRegistryKeyPath</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\{0}\\TeamFoundation\\SourceControl\\Checkin Policies"</span><span class="p">,</span> <span class="n">visualStudioVersionNumber</span><span class="p">);</span>
        <span class="k">else</span>
            <span class="n">currentRegistryKeyPath</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\{0}\\TeamFoundation\\SourceControl\\Checkin Policies"</span><span class="p">,</span> <span class="n">visualStudioVersionNumber</span><span class="p">);</span>

        <span class="c1">// Get the value that the registry should have, and the value that it currently has.</span>
        <span class="kt">var</span> <span class="n">desiredRegistryValue</span> <span class="p">=</span> <span class="n">Registry</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">desiredRegistryKeyPath</span><span class="p">,</span> <span class="n">customCheckinPolicyEntryName</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span>
        <span class="kt">var</span> <span class="n">currentRegistryValue</span> <span class="p">=</span> <span class="n">Registry</span><span class="p">.</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">currentRegistryKeyPath</span><span class="p">,</span> <span class="n">customCheckinPolicyEntryName</span><span class="p">,</span> <span class="k">null</span><span class="p">);</span>

        <span class="c1">// If the registry value is already up to date, just exit without updating the registry.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">desiredRegistryValue</span> <span class="p">==</span> <span class="k">null</span> <span class="p">||</span> <span class="n">desiredRegistryValue</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">currentRegistryValue</span><span class="p">))</span>
            <span class="k">return</span><span class="p">;</span>

        <span class="c1">// Get the path to the PowerShell script to run.</span>
        <span class="kt">string</span> <span class="n">powerShellScriptFilePath</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">GetDirectoryName</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">Reflection</span><span class="p">.</span><span class="n">Assembly</span><span class="p">.</span><span class="nf">GetAssembly</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">CheckinPolicyDeploymentPackage</span><span class="p">)).</span><span class="n">Location</span><span class="p">),</span>
            <span class="s">"FilesFromShared"</span><span class="p">,</span> <span class="s">"UpdateCheckinPolicyInRegistry.ps1"</span><span class="p">);</span>

        <span class="c1">// Start a new process to execute the batch file script, which calls the PowerShell script to do the actual work.</span>
        <span class="kt">var</span> <span class="n">process</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Process</span>
        <span class="p">{</span>
            <span class="n">StartInfo</span> <span class="p">=</span>
            <span class="p">{</span>
                <span class="n">FileName</span> <span class="p">=</span> <span class="s">"PowerShell"</span><span class="p">,</span>
                <span class="n">Arguments</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">"-NoProfile -ExecutionPolicy Bypass -File \"{0}\" -VisualStudioVersion \"{1}\" -CustomCheckinPolicyEntryName \"{2}\""</span><span class="p">,</span> <span class="n">powerShellScriptFilePath</span><span class="p">,</span> <span class="n">visualStudioVersionNumber</span><span class="p">,</span> <span class="n">customCheckinPolicyEntryName</span><span class="p">),</span>

                <span class="c1">// Hide the PowerShell window while we run the script.</span>
                <span class="n">CreateNoWindow</span> <span class="p">=</span> <span class="k">true</span><span class="p">,</span>
                <span class="n">UseShellExecute</span> <span class="p">=</span> <span class="k">false</span>
            <span class="p">}</span>
        <span class="p">};</span>
        <span class="n">process</span><span class="p">.</span><span class="nf">Start</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>All of the attributes on the class are put there by default, except for the “[ProvideAutoLoad(“ADFC4E64-0397-11D1-9F4E-00A0C911004F”)]” one; this attribute is the one that actually allows the Initialize() function to get called when Visual Studio starts. You can see in the Initialize method that we hook up an event so that the UpdateCheckinPoliciesInRegistry() function gets called when VS is closed, and we also call that function from Initialize(), which is called when VS starts up.</p>

<p>You might have noticed that this class is abstract. This is because the VS 2012 and VS 2013 classed need to have a unique ID attribute, so the actual VSPackage class just inherits from this one. Here is what the VS 2013 one looks like:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Guid</span><span class="p">(</span><span class="n">GuidList</span><span class="p">.</span><span class="n">guidCheckinPolicyDeployment_VS2013PkgString</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">sealed</span> <span class="k">class</span> <span class="nc">CheckinPolicyDeployment_VS2013Package</span> <span class="p">:</span> <span class="n">CheckinPolicyDeploymentShared</span><span class="p">.</span><span class="n">CheckinPolicyDeploymentPackage</span>
<span class="p">{</span> <span class="p">}</span>
</code></pre></div></div>

<p>The UpdateCheckinPoliciesInRegistry() function checks to see if the appropriate registry key has been updated to allow the checkin policies to run from the “tf checkin” command prompt command. If they have, then it simply exits, otherwise it calls a PowerShell script to set the keys for us. A PowerShell script is used because modifying the registry requires admin permissions, and we can easily run a new PowerShell process as admin (assuming the logged in user is an admin on their local machine, which is the case for everyone in our company).</p>

<p>The one variable to note here is the <strong>customCheckinPolicyEntryName</strong>. This corresponds to the registry key name that I’ve specified in the RegistryKeyToAdd.pkgdef file, so if you change it be sure to change it in both places. This is what the RegistryKeyToAdd.pkgdef file contains:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// We use "\..\" in the value because the projects that include this file place it in a "FilesFromShared" folder, and we want it to look for the dll in the root directory.</span>
<span class="p">[</span><span class="err">$</span><span class="n">RootKey</span><span class="err">$\</span><span class="n">TeamFoundation</span><span class="err">\</span><span class="n">SourceControl</span><span class="err">\</span><span class="n">Checkin</span> <span class="n">Policies</span><span class="p">]</span>
<span class="s">"CheckinPolicies"</span><span class="p">=</span><span class="s">"$PackageFolder$\..\CheckinPolicies.dll"</span>
</code></pre></div></div>

<p>And here are the contents of the UpdateCheckinPolicyInRegistry.ps1 PowerShell file. This is basically just a refactored version of the script I posted on my old blog post:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This script copies the required registry value so that the checkin policies will work when doing a TFS checkin from the command line.</span><span class="w">
</span><span class="kr">param</span><span class="w">
</span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">,</span><span class="n">HelpMessage</span><span class="o">=</span><span class="s2">"The version of Visual Studio to update in the registry (i.e. '11.0' for VS 2012, '12.0' for VS 2013)"</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$VisualStudioVersion</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">HelpMessage</span><span class="o">=</span><span class="s2">"The name of the Custom Checkin Policy Entry in the Registry Key."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'CheckinPolicies'</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c"># Turn on Strict Mode to help catch syntax-related errors.</span><span class="w">
</span><span class="c">#   This must come after a script's/function's param section.</span><span class="w">
</span><span class="c">#   Forces a function to be the first non-comment code to appear in a PowerShell Module.</span><span class="w">
</span><span class="n">Set-StrictMode</span><span class="w"> </span><span class="nt">-Version</span><span class="w"> </span><span class="nx">Latest</span><span class="w">

</span><span class="nv">$ScriptBlock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">function</span><span class="w"> </span><span class="nf">UpdateCheckinPolicyInRegistry</span><span class="p">([</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)][</span><span class="n">string</span><span class="p">]</span><span class="nv">$VisualStudioVersion</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Updating registry to allow checkin policies to work outside of Visual Studio version '</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$VisualStudioVersion</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">'.'</span><span class="w">
        </span><span class="n">Write-Output</span><span class="w"> </span><span class="nv">$status</span><span class="w">

        </span><span class="c"># Get the Registry Key Entry that holds the path to the Custom Checkin Policy Assembly.</span><span class="w">
        </span><span class="nv">$HKCUKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'HKCU:\Software\Microsoft\VisualStudio\'</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$VisualStudioVersion</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">'_Config\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w">
        </span><span class="nv">$CustomCheckinPolicyRegistryEntry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$HKCUKey</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="w">
        </span><span class="nv">$CustomCheckinPolicyEntryValue</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyRegistryEntry</span><span class="o">.</span><span class="p">(</span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="p">)</span><span class="w">

        </span><span class="c"># Create a new Registry Key Entry for the iQ Checkin Policy Assembly so they will work from the command line (as well as from Visual Studio).</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">Environment</span><span class="p">]::</span><span class="n">Is64BitOperatingSystem</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nv">$HKLMKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'HKLM:\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\'</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$VisualStudioVersion</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">'\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w"> </span><span class="nv">$HKLMKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'HKLM:\SOFTWARE\Microsoft\VisualStudio\'</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$VisualStudioVersion</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">'\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="n">Set-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$HKLMKey</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyEntryValue</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Run the script block as admin so it has permissions to modify the registry.</span><span class="w">
</span><span class="n">Start-Process</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">PowerShell</span><span class="w"> </span><span class="nt">-Verb</span><span class="w"> </span><span class="nx">RunAs</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="s2">"-NoProfile -ExecutionPolicy Bypass -Command &amp;amp; {</span><span class="nv">$ScriptBlock</span><span class="s2"> UpdateCheckinPolicyInRegistry -VisualStudioVersion ""</span><span class="nv">$VisualStudioVersion</span><span class="s2">"" -CustomCheckinPolicyEntryName ""</span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="s2">""}"</span><span class="w">
</span></code></pre></div></div>

<p>While I could have just used a much smaller PowerShell script that simply set a given registry key to a given value, I chose to have some code duplication between the C# code and this script so that this script can still be used as a stand-alone script if needed.</p>

<p>The slight downside to using a VSPackage is that this script still won’t get called until the user closes or opens a new instance of Visual Studio, so the checkin policies won’t work immediately from the “tf checkin” command after updating the checkin policies extension, but this still beats having to remember to manually run the script.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So I’ve given you a template solution that you can use without any modification to start creating your VS 2012 and VS 2013 compatible checkin policies; Just add new class files to the CheckinPolicies.VS2013 project, and then add them to the CheckinPolicies.VS2012 project as well <strong>as links</strong>. By using links it allows you to only have to modify checkin policy files once, and have the changes go to both the 2012 and 2013 VSIX packages. Hopefully this template solution helps you to get your TFS checkin policies up and running faster.</p>

<p>Happy Coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="TFS" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="Checkin" /><category term="Checkin Policies" /><category term="PowerShell" /><category term="Source Code" /><category term="Team Foundation" /><category term="Team Foundation Server" /><category term="TF" /><category term="TF.exe" /><category term="TFS" /><category term="Visual Studio" /><category term="VSIX" /><summary type="html"><![CDATA[Get the source code]]></summary></entry><entry><title type="html">Saving And Loading A C# Object’s Data To An Xml, Json, Or Binary File</title><link href="https://blog.danskingdom.com/saving-and-loading-a-c-objects-data-to-an-xml-json-or-binary-file/" rel="alternate" type="text/html" title="Saving And Loading A C# Object’s Data To An Xml, Json, Or Binary File" /><published>2014-03-14T21:53:35+00:00</published><updated>2014-03-14T21:53:35+00:00</updated><id>https://blog.danskingdom.com/saving-and-loading-a-c-objects-data-to-an-xml-json-or-binary-file</id><content type="html" xml:base="https://blog.danskingdom.com/saving-and-loading-a-c-objects-data-to-an-xml-json-or-binary-file/"><![CDATA[<p>I love creating tools, particularly ones for myself and other developers to use. A common situation that I run into is needing to save the user’s settings to a file so that I can load them up the next time the tool is ran. I find that the easiest way to accomplish this is to create a Settings class to hold all of the user’s settings, and then use serialization to save and load the class instance to/from a file. I mention a Settings class here, but you can use this technique to save any object (or list of objects) to a file.</p>

<p>There are tons of different formats that you may want to save your object instances as, but the big three are Binary, XML, and Json. Each of these formats have their pros and cons, which I won’t go into. Below I present functions that can be used to save and load any object instance to / from a file, as well as the different aspects to be aware of when using each method.</p>

<p>The follow code (without examples of how to use it) <a href="https://dansutilitylibraries.codeplex.com/SourceControl/latest#DansUtilityLibraries/DansCSharpLibrary/Serialization/BinarySerialization.cs">is also available here</a>, and can be used directly from <a href="https://www.nuget.org/packages/DansUtilityLibraries.CSharpLibrary/">my NuGet Package</a>.</p>

<h2 id="writing-and-reading-an-object-to--from-a-binary-file">Writing and Reading an object to / from a Binary file</h2>

<blockquote>
  <p>UPDATE: BinaryFormatter is being decommissioned due to security vulnerabilities it introduces, so it should no longer be used.
<a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide">Read more on the MS docs here</a></p>
</blockquote>

<ul>
  <li>Writes and reads ALL object properties and variables to / from the file (i.e. public, protected, internal, and private).</li>
  <li>The data saved to the file is not human readable, and thus cannot be edited outside of your application.</li>
  <li>Have to decorate class (and all classes that it contains) with a <strong>[Serializable]</strong> attribute.</li>
  <li>Use the <strong>[NonSerialized]</strong> attribute to exclude a variable from being written to the file; there is no way to prevent an auto-property from being serialized besides making it use a backing variable and putting the [NonSerialized] attribute on that.</li>
</ul>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Functions for performing common binary Serialization operations.</span>
<span class="c1">/// &lt;para&gt;All properties and variables will be serialized.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;Object type (and all child types) must be decorated with the [Serializable] attribute.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;To prevent a variable from being serialized, decorate it with the [NonSerialized] attribute; cannot be applied to properties.&lt;/para&gt;</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">BinarySerialization</span>
<span class="p">{</span>
    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Writes the given object instance to a binary file.</span>
    <span class="c1">/// &lt;para&gt;Object type (and all child types) must be decorated with the [Serializable] attribute.&lt;/para&gt;</span>
    <span class="c1">/// &lt;para&gt;To prevent a variable from being serialized, decorate it with the [NonSerialized] attribute; cannot be applied to properties.&lt;/para&gt;</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;typeparam name="T"&gt;The type of object being written to the XML file.&lt;/typeparam&gt;</span>
    <span class="c1">/// &lt;param name="filePath"&gt;The file path to write the object instance to.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="objectToWrite"&gt;The object instance to write to the XML file.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="append"&gt;If false the file will be overwritten if it already exists. If true the contents will be appended to the file.&lt;/param&gt;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="n">WriteToBinaryFile</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">filePath</span><span class="p">,</span> <span class="n">T</span> <span class="n">objectToWrite</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">append</span> <span class="p">=</span> <span class="k">false</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">using</span> <span class="p">(</span><span class="n">Stream</span> <span class="n">stream</span> <span class="p">=</span> <span class="n">File</span><span class="p">.</span><span class="nf">Open</span><span class="p">(</span><span class="n">filePath</span><span class="p">,</span> <span class="n">append</span> <span class="p">?</span> <span class="n">FileMode</span><span class="p">.</span><span class="n">Append</span> <span class="p">:</span> <span class="n">FileMode</span><span class="p">.</span><span class="n">Create</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">binaryFormatter</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Runtime</span><span class="p">.</span><span class="n">Serialization</span><span class="p">.</span><span class="n">Formatters</span><span class="p">.</span><span class="n">Binary</span><span class="p">.</span><span class="nf">BinaryFormatter</span><span class="p">();</span>
            <span class="n">binaryFormatter</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">stream</span><span class="p">,</span> <span class="n">objectToWrite</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Reads an object instance from a binary file.</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;typeparam name="T"&gt;The type of object to read from the XML.&lt;/typeparam&gt;</span>
    <span class="c1">/// &lt;param name="filePath"&gt;The file path to read the object instance from.&lt;/param&gt;</span>
    <span class="c1">/// &lt;returns&gt;Returns a new instance of the object read from the binary file.&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">T</span> <span class="n">ReadFromBinaryFile</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">filePath</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">using</span> <span class="p">(</span><span class="n">Stream</span> <span class="n">stream</span> <span class="p">=</span> <span class="n">File</span><span class="p">.</span><span class="nf">Open</span><span class="p">(</span><span class="n">filePath</span><span class="p">,</span> <span class="n">FileMode</span><span class="p">.</span><span class="n">Open</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">binaryFormatter</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Runtime</span><span class="p">.</span><span class="n">Serialization</span><span class="p">.</span><span class="n">Formatters</span><span class="p">.</span><span class="n">Binary</span><span class="p">.</span><span class="nf">BinaryFormatter</span><span class="p">();</span>
            <span class="k">return</span> <span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="n">binaryFormatter</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">stream</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here is an example of how to use it:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Serializable</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Age</span> <span class="p">=</span> <span class="m">20</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Address</span> <span class="n">HomeAddress</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;}</span>
    <span class="k">private</span> <span class="kt">string</span> <span class="n">_thisWillGetWrittenToTheFileToo</span> <span class="p">=</span> <span class="s">"even though it is a private variable."</span><span class="p">;</span>

    <span class="p">[</span><span class="n">NonSerialized</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ThisWillNotBeWrittenToTheFile</span> <span class="p">=</span> <span class="s">"because of the [NonSerialized] attribute."</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">[</span><span class="n">Serializable</span><span class="p">]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Address</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">StreetAddress</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">City</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// And then in some function.</span>
<span class="n">Person</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Dan"</span><span class="p">,</span> <span class="n">Age</span> <span class="p">=</span> <span class="m">30</span><span class="p">;</span> <span class="n">HomeAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Address</span><span class="p">()</span> <span class="p">{</span> <span class="n">StreetAddress</span> <span class="p">=</span> <span class="s">"123 My St"</span><span class="p">,</span> <span class="n">City</span> <span class="p">=</span> <span class="s">"Regina"</span> <span class="p">}};</span>
<span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;</span> <span class="n">people</span> <span class="p">=</span> <span class="nf">GetListOfPeople</span><span class="p">();</span>
<span class="n">BinarySerialization</span><span class="p">.</span><span class="n">WriteToBinaryFile</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;(</span><span class="s">"C:\person.bin"</span><span class="p">,</span> <span class="n">person</span><span class="p">);</span>
<span class="n">BinarySerialization</span><span class="p">.</span><span class="n">WriteToBinaryFile</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">People</span><span class="p">&gt;&gt;(</span><span class="s">"C:\people.bin"</span><span class="p">,</span> <span class="n">people</span><span class="p">);</span>

<span class="c1">// Then in some other function.</span>
<span class="n">Person</span> <span class="n">person</span> <span class="p">=</span> <span class="n">BinarySerialization</span><span class="p">.</span><span class="n">ReadFromBinaryFile</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;(</span><span class="s">"C:\person.bin"</span><span class="p">);</span>
<span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;</span> <span class="n">people</span> <span class="p">=</span> <span class="n">BinarySerialization</span><span class="p">.</span><span class="n">ReadFromBinaryFile</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;&gt;(</span><span class="s">"C:\people.bin"</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="writing-and-reading-an-object-to--from-an-xml-file-using-systemxmlserializationxmlserializer-in-the-systemxml-assembly">Writing and Reading an object to / from an XML file (Using System.Xml.Serialization.XmlSerializer in the System.Xml assembly)</h2>

<ul>
  <li>Only writes and reads the Public properties and variables to / from the file.</li>
  <li>Classes to be serialized must contain a public parameterless constructor.</li>
  <li>The data saved to the file is human readable, so it can easily be edited outside of your application.</li>
  <li>Use the <strong>[XmlIgnore]</strong> attribute to exclude a public property or variable from being written to the file.</li>
</ul>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Functions for performing common XML Serialization operations.</span>
<span class="c1">/// &lt;para&gt;Only public properties and variables will be serialized.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;Use the [XmlIgnore] attribute to prevent a property/variable from being serialized.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;Object to be serialized must have a parameterless constructor.&lt;/para&gt;</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">XmlSerialization</span>
<span class="p">{</span>
    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Writes the given object instance to an XML file.</span>
    <span class="c1">/// &lt;para&gt;Only Public properties and variables will be written to the file. These can be any type though, even other classes.&lt;/para&gt;</span>
    <span class="c1">/// &lt;para&gt;If there are public properties/variables that you do not want written to the file, decorate them with the [XmlIgnore] attribute.&lt;/para&gt;</span>
    <span class="c1">/// &lt;para&gt;Object type must have a parameterless constructor.&lt;/para&gt;</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;typeparam name="T"&gt;The type of object being written to the file.&lt;/typeparam&gt;</span>
    <span class="c1">/// &lt;param name="filePath"&gt;The file path to write the object instance to.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="objectToWrite"&gt;The object instance to write to the file.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="append"&gt;If false the file will be overwritten if it already exists. If true the contents will be appended to the file.&lt;/param&gt;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="n">WriteToXmlFile</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">filePath</span><span class="p">,</span> <span class="n">T</span> <span class="n">objectToWrite</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">append</span> <span class="p">=</span> <span class="k">false</span><span class="p">)</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">new</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">TextWriter</span> <span class="n">writer</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
        <span class="k">try</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">serializer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">XmlSerializer</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">T</span><span class="p">));</span>
            <span class="n">writer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StreamWriter</span><span class="p">(</span><span class="n">filePath</span><span class="p">,</span> <span class="n">append</span><span class="p">);</span>
            <span class="n">serializer</span><span class="p">.</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="n">objectToWrite</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">finally</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">writer</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
                <span class="n">writer</span><span class="p">.</span><span class="nf">Close</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Reads an object instance from an XML file.</span>
    <span class="c1">/// &lt;para&gt;Object type must have a parameterless constructor.&lt;/para&gt;</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;typeparam name="T"&gt;The type of object to read from the file.&lt;/typeparam&gt;</span>
    <span class="c1">/// &lt;param name="filePath"&gt;The file path to read the object instance from.&lt;/param&gt;</span>
    <span class="c1">/// &lt;returns&gt;Returns a new instance of the object read from the XML file.&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">T</span> <span class="n">ReadFromXmlFile</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">filePath</span><span class="p">)</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">new</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">TextReader</span> <span class="n">reader</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
        <span class="k">try</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">serializer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">XmlSerializer</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">T</span><span class="p">));</span>
            <span class="n">reader</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StreamReader</span><span class="p">(</span><span class="n">filePath</span><span class="p">);</span>
            <span class="k">return</span> <span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="n">serializer</span><span class="p">.</span><span class="nf">Deserialize</span><span class="p">(</span><span class="n">reader</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">finally</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">reader</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
                <span class="n">reader</span><span class="p">.</span><span class="nf">Close</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here is an example of how to use it:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Age</span> <span class="p">=</span> <span class="m">20</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Address</span> <span class="n">HomeAddress</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;}</span>
    <span class="k">private</span> <span class="kt">string</span> <span class="n">_thisWillNotGetWrittenToTheFile</span> <span class="p">=</span> <span class="s">"because it is not public."</span><span class="p">;</span>

    <span class="p">[</span><span class="n">XmlIgnore</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ThisWillNotBeWrittenToTheFile</span> <span class="p">=</span> <span class="s">"because of the [XmlIgnore] attribute."</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">Address</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">StreetAddress</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">City</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// And then in some function.</span>
<span class="n">Person</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Dan"</span><span class="p">,</span> <span class="n">Age</span> <span class="p">=</span> <span class="m">30</span><span class="p">;</span> <span class="n">HomeAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Address</span><span class="p">()</span> <span class="p">{</span> <span class="n">StreetAddress</span> <span class="p">=</span> <span class="s">"123 My St"</span><span class="p">,</span> <span class="n">City</span> <span class="p">=</span> <span class="s">"Regina"</span> <span class="p">}};</span>
<span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;</span> <span class="n">people</span> <span class="p">=</span> <span class="nf">GetListOfPeople</span><span class="p">();</span>
<span class="n">XmlSerialization</span><span class="p">.</span><span class="n">WriteToXmlFile</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;(</span><span class="s">"C:\person.txt"</span><span class="p">,</span> <span class="n">person</span><span class="p">);</span>
<span class="n">XmlSerialization</span><span class="p">.</span><span class="n">WriteToXmlFile</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">People</span><span class="p">&gt;&gt;(</span><span class="s">"C:\people.txt"</span><span class="p">,</span> <span class="n">people</span><span class="p">);</span>

<span class="c1">// Then in some other function.</span>
<span class="n">Person</span> <span class="n">person</span> <span class="p">=</span> <span class="n">XmlSerialization</span><span class="p">.</span><span class="n">ReadFromXmlFile</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;(</span><span class="s">"C:\person.txt"</span><span class="p">);</span>
<span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;</span> <span class="n">people</span> <span class="p">=</span> <span class="n">XmlSerialization</span><span class="p">.</span><span class="n">ReadFromXmlFile</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;&gt;(</span><span class="s">"C:\people.txt"</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="writing-and-reading-an-object-to--from-a-json-file-using-the-newtonsoftjson-assembly-in-the-jsonnet-nuget-package">Writing and Reading an object to / from a Json file (using the <a href="http://james.newtonking.com/json">Newtonsoft.Json</a> assembly in the <a href="http://www.nuget.org/packages/Newtonsoft.Json/">Json.NET NuGet package</a>)</h2>

<blockquote>
  <p>UPDATE: As an alternative to Newtonsoft.Json, the <code class="language-plaintext highlighter-rouge">System.Text.Json</code> class is included out of the box starting in .NET Core 3.
If it is not available in your .NET version, you can use <a href="https://www.nuget.org/packages/System.Text.Json">the System.Text.Json NuGet package</a>.
See the <a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft">Json.NET to System.Text.Json migration guide</a> for more information.</p>
</blockquote>

<ul>
  <li>Only writes and reads the Public properties and variables to / from the file.</li>
  <li>Classes to be serialized must contain a public parameterless constructor.</li>
  <li>The data saved to the file is human readable, so it can easily be edited outside of your application.</li>
  <li>Use the <strong>[JsonIgnore]</strong> attribute to exclude a public property or variable from being written to the file.</li>
</ul>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// &lt;summary&gt;</span>
<span class="c1">/// Functions for performing common Json Serialization operations.</span>
<span class="c1">/// &lt;para&gt;Requires the Newtonsoft.Json assembly (Json.Net package in NuGet Gallery) to be referenced in your project.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;Only public properties and variables will be serialized.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;Use the [JsonIgnore] attribute to ignore specific public properties or variables.&lt;/para&gt;</span>
<span class="c1">/// &lt;para&gt;Object to be serialized must have a parameterless constructor.&lt;/para&gt;</span>
<span class="c1">/// &lt;/summary&gt;</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">JsonSerialization</span>
<span class="p">{</span>
    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Writes the given object instance to a Json file.</span>
    <span class="c1">/// &lt;para&gt;Object type must have a parameterless constructor.&lt;/para&gt;</span>
    <span class="c1">/// &lt;para&gt;Only Public properties and variables will be written to the file. These can be any type though, even other classes.&lt;/para&gt;</span>
    <span class="c1">/// &lt;para&gt;If there are public properties/variables that you do not want written to the file, decorate them with the [JsonIgnore] attribute.&lt;/para&gt;</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;typeparam name="T"&gt;The type of object being written to the file.&lt;/typeparam&gt;</span>
    <span class="c1">/// &lt;param name="filePath"&gt;The file path to write the object instance to.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="objectToWrite"&gt;The object instance to write to the file.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="append"&gt;If false the file will be overwritten if it already exists. If true the contents will be appended to the file.&lt;/param&gt;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="n">WriteToJsonFile</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">filePath</span><span class="p">,</span> <span class="n">T</span> <span class="n">objectToWrite</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">append</span> <span class="p">=</span> <span class="k">false</span><span class="p">)</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">new</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">TextWriter</span> <span class="n">writer</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
        <span class="k">try</span>
        <span class="p">{</span>
            <span class="kt">var</span> <span class="n">contentsToWriteToFile</span> <span class="p">=</span> <span class="n">Newtonsoft</span><span class="p">.</span><span class="n">Json</span><span class="p">.</span><span class="n">JsonConvert</span><span class="p">.</span><span class="nf">SerializeObject</span><span class="p">(</span><span class="n">objectToWrite</span><span class="p">);</span>
            <span class="n">writer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StreamWriter</span><span class="p">(</span><span class="n">filePath</span><span class="p">,</span> <span class="n">append</span><span class="p">);</span>
            <span class="n">writer</span><span class="p">.</span><span class="nf">Write</span><span class="p">(</span><span class="n">contentsToWriteToFile</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">finally</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">writer</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
                <span class="n">writer</span><span class="p">.</span><span class="nf">Close</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Reads an object instance from an Json file.</span>
    <span class="c1">/// &lt;para&gt;Object type must have a parameterless constructor.&lt;/para&gt;</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;typeparam name="T"&gt;The type of object to read from the file.&lt;/typeparam&gt;</span>
    <span class="c1">/// &lt;param name="filePath"&gt;The file path to read the object instance from.&lt;/param&gt;</span>
    <span class="c1">/// &lt;returns&gt;Returns a new instance of the object read from the Json file.&lt;/returns&gt;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">T</span> <span class="n">ReadFromJsonFile</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">filePath</span><span class="p">)</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">new</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">TextReader</span> <span class="n">reader</span> <span class="p">=</span> <span class="k">null</span><span class="p">;</span>
        <span class="k">try</span>
        <span class="p">{</span>
            <span class="n">reader</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StreamReader</span><span class="p">(</span><span class="n">filePath</span><span class="p">);</span>
            <span class="kt">var</span> <span class="n">fileContents</span> <span class="p">=</span> <span class="n">reader</span><span class="p">.</span><span class="nf">ReadToEnd</span><span class="p">();</span>
            <span class="k">return</span> <span class="n">Newtonsoft</span><span class="p">.</span><span class="n">Json</span><span class="p">.</span><span class="n">JsonConvert</span><span class="p">.</span><span class="n">DeserializeObject</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">fileContents</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">finally</span>
        <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">reader</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
                <span class="n">reader</span><span class="p">.</span><span class="nf">Close</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here is an example of how to use it:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">int</span> <span class="n">Age</span> <span class="p">=</span> <span class="m">20</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">Address</span> <span class="n">HomeAddress</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;}</span>
    <span class="k">private</span> <span class="kt">string</span> <span class="n">_thisWillNotGetWrittenToTheFile</span> <span class="p">=</span> <span class="s">"because it is not public."</span><span class="p">;</span>

    <span class="p">[</span><span class="n">JsonIgnore</span><span class="p">]</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">ThisWillNotBeWrittenToTheFile</span> <span class="p">=</span> <span class="s">"because of the [JsonIgnore] attribute."</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">Address</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">StreetAddress</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">City</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// And then in some function.</span>
<span class="n">Person</span> <span class="n">person</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Person</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="s">"Dan"</span><span class="p">,</span> <span class="n">Age</span> <span class="p">=</span> <span class="m">30</span><span class="p">;</span> <span class="n">HomeAddress</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Address</span><span class="p">()</span> <span class="p">{</span> <span class="n">StreetAddress</span> <span class="p">=</span> <span class="s">"123 My St"</span><span class="p">,</span> <span class="n">City</span> <span class="p">=</span> <span class="s">"Regina"</span> <span class="p">}};</span>
<span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;</span> <span class="n">people</span> <span class="p">=</span> <span class="nf">GetListOfPeople</span><span class="p">();</span>
<span class="n">JsonSerialization</span><span class="p">.</span><span class="n">WriteToJsonFile</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;(</span><span class="s">"C:\person.txt"</span><span class="p">,</span> <span class="n">person</span><span class="p">);</span>
<span class="n">JsonSerialization</span><span class="p">.</span><span class="n">WriteToJsonFile</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">People</span><span class="p">&gt;&gt;(</span><span class="s">"C:\people.txt"</span><span class="p">,</span> <span class="n">people</span><span class="p">);</span>

<span class="c1">// Then in some other function.</span>
<span class="n">Person</span> <span class="n">person</span> <span class="p">=</span> <span class="n">JsonSerialization</span><span class="p">.</span><span class="n">ReadFromJsonFile</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;(</span><span class="s">"C:\person.txt"</span><span class="p">);</span>
<span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;</span> <span class="n">people</span> <span class="p">=</span> <span class="n">JsonSerialization</span><span class="p">.</span><span class="n">ReadFromJsonFile</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">Person</span><span class="p">&gt;&gt;(</span><span class="s">"C:\people.txt"</span><span class="p">);</span>
</code></pre></div></div>

<p>As you can see, the Json example is almost identical to the Xml example, with the exception of using the [JsonIgnore] attribute instead of [XmlIgnore].</p>

<h2 id="writing-and-reading-an-object-to--from-a-json-file-using-the-javascriptserializer-in-the-systemwebextensions-assembly">Writing and Reading an object to / from a Json file (using the <a href="http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer%28v=vs.110%29.aspx">JavaScriptSerializer</a> in the System.Web.Extensions assembly)</h2>

<p>There are many Json serialization libraries out there. I mentioned the Newtonsoft.Json one because it is very popular, and I am also mentioning this JavaScriptSerializer one because it is built into the .Net framework. The catch with this one though is that it requires the Full .Net 4.0 framework, not just the .Net Framework 4.0 Client Profile.</p>

<p>The caveats to be aware of are the same between the Newtonsoft.Json and JavaScriptSerializer libraries, except instead of using [JsonIgnore] you would use <strong>[ScriptIgnore]</strong>.</p>

<p>Be aware that the JavaScriptSerializer is in the System.Web.Extensions assembly, but in the System.Web.Script.Serialization namespace. Here is the code from the Newtonsoft.Json code snippet that needs to be replaced in order to use the JavaScriptSerializer:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// In WriteFromJsonFile&lt;T&gt;() function replace:</span>
<span class="kt">var</span> <span class="n">contentsToWriteToFile</span> <span class="p">=</span> <span class="n">Newtonsoft</span><span class="p">.</span><span class="n">Json</span><span class="p">.</span><span class="n">JsonConvert</span><span class="p">.</span><span class="nf">SerializeObject</span><span class="p">(</span><span class="n">objectToWrite</span><span class="p">);</span>
<span class="c1">// with:</span>
<span class="kt">var</span> <span class="n">contentsToWriteToFile</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Web</span><span class="p">.</span><span class="n">Script</span><span class="p">.</span><span class="n">Serialization</span><span class="p">.</span><span class="nf">JavaScriptSerializer</span><span class="p">().</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">objectToWrite</span><span class="p">);</span>

<span class="c1">// In ReadFromJsonFile&lt;T&gt;() function replace:</span>
<span class="k">return</span> <span class="n">Newtonsoft</span><span class="p">.</span><span class="n">Json</span><span class="p">.</span><span class="n">JsonConvert</span><span class="p">.</span><span class="n">DeserializeObject</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">fileContents</span><span class="p">);</span>
<span class="c1">// with:</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Web</span><span class="p">.</span><span class="n">Script</span><span class="p">.</span><span class="n">Serialization</span><span class="p">.</span><span class="nf">JavaScriptSerializer</span><span class="p">().</span><span class="n">Deserialize</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">fileContents</span><span class="p">);</span>
</code></pre></div></div>

<p>Happy Coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="CSharp" /><category term="Json" /><category term="XML" /><category term="Binary" /><category term="Class" /><category term="file" /><category term="Json" /><category term="List" /><category term="Load" /><category term="object" /><category term="Read" /><category term="Save" /><category term="Settings" /><category term="Write" /><category term="XML" /><summary type="html"><![CDATA[I love creating tools, particularly ones for myself and other developers to use. A common situation that I run into is needing to save the user’s settings to a file so that I can load them up the next time the tool is ran. I find that the easiest way to accomplish this is to create a Settings class to hold all of the user’s settings, and then use serialization to save and load the class instance to/from a file. I mention a Settings class here, but you can use this technique to save any object (or list of objects) to a file.]]></summary></entry><entry><title type="html">“Agent lost communication with Team Foundation Server” TFS Build Server Error</title><link href="https://blog.danskingdom.com/agent-lost-communication-with-team-foundation-server-build-server-error/" rel="alternate" type="text/html" title="“Agent lost communication with Team Foundation Server” TFS Build Server Error" /><published>2014-03-12T17:26:51+00:00</published><updated>2014-03-12T17:26:51+00:00</updated><id>https://blog.danskingdom.com/agent-lost-communication-with-team-foundation-server-build-server-error</id><content type="html" xml:base="https://blog.danskingdom.com/agent-lost-communication-with-team-foundation-server-build-server-error/"><![CDATA[<p>We had recently started getting lots of error messages similar to the following on our TFS Build Servers:</p>

<blockquote>
  <p>Exception Message: The build failed because the build server that hosts build agent TFS-BuildController001 - Agent4 lost communication with Team Foundation Server. (type FaultException`1)</p>
</blockquote>

<p>This error message would appear randomly; some builds would pass, others would fail, and when they did fail with this error message it was often at different parts in the build process.</p>

<p>After a bit of digging I found <a href="http://social.technet.microsoft.com/Forums/windowsserver/en-US/cd99a033-e787-4b7a-9a50-8e02af8d7047/visual-studio-keeps-losing-connection-to-team-foundation-server?forum=winservergen">this post</a> and <a href="http://social.msdn.microsoft.com/Forums/vstudio/en-US/6d33f92e-2a61-4584-976e-3c865cdde72c/tfs-2010-sp1-build-process-fails-with-team-foundation-services-are-not-available-from-server?forum=tfsbuild">this one</a>, which discussed different error messages around their build process failing with some sort of error around the build controller losing connection to the TFS server. They talked about different fixes relating to DNS issues and load balancing, so we had our network team update our DNS records and flush the cache, but were still getting the same errors.</p>

<p>We have several build controllers, and I noticed that the problem was only happening on two of the three, so our network team updated the <strong>hosts</strong> file on the two with the problem to match the entries in the one that was working fine, and boom, everything started working properly again 🙂</p>

<p>So the problem was that the hosts file on those two build controller machines somehow got changed.</p>

<p>The hosts file can typically be found at “C:\Windows\System32\Drivers\etc\hosts”, and here is an example of what we now have in our hosts file for entries (just the two entries):</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>12.345.67.89    TFS-Server.OurDomain.local
12.345.67.89    TFS-Server
</code></pre></div></div>

<p>If you too are running into this TFS Build Server error I hope this helps.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="TFS" /><category term="Build" /><category term="Build Controller" /><category term="Build Server" /><category term="Communication" /><category term="Connection" /><category term="error" /><category term="Hosts File" /><category term="Lost" /><category term="Team Foundation" /><category term="Team Foundation Server" /><category term="TFS" /><summary type="html"><![CDATA[We had recently started getting lots of error messages similar to the following on our TFS Build Servers:]]></summary></entry><entry><title type="html">If You Like Using Macros or AutoHotkey, You Might Want To Try The Enterpad AHK Keyboard</title><link href="https://blog.danskingdom.com/if-you-like-using-macros-or-autohotkey-you-might-want-to-try-the-enterpad-ahk-keyboard/" rel="alternate" type="text/html" title="If You Like Using Macros or AutoHotkey, You Might Want To Try The Enterpad AHK Keyboard" /><published>2014-02-13T03:24:09+00:00</published><updated>2014-02-13T03:24:09+00:00</updated><id>https://blog.danskingdom.com/if-you-like-using-macros-or-autohotkey-you-might-want-to-try-the-enterpad-ahk-keyboard</id><content type="html" xml:base="https://blog.danskingdom.com/if-you-like-using-macros-or-autohotkey-you-might-want-to-try-the-enterpad-ahk-keyboard/"><![CDATA[<p>If you follow my blog then you already know I’m a huge fan of <a href="http://www.autohotkey.com">AutoHotkey</a> (AHK), and that I created the <a href="http://ahkcommandpicker.codeplex.com">AHK Command Picker</a> to allow me to have a limitless number of AHK macros quickly and easily accessible from my keyboard, without having a bunch of hotkeys (i.e. keyboard shortcuts) to remember. The team over at CEDEQ saw my blog posts and were kind enough to send me an <a href="http://cedeq.com/enterpad/en/autohotkey/">Enterpad AHK Keyboard</a> for free :-)</p>

<h2 id="what-is-the-enterpad-ahk-keyboard">What is the Enterpad AHK Keyboard?</h2>

<p>The Enterpad AHK keyboard is a physical device with 120 different touch spots on it, each of which can be used to trigger a different AHK macro/script. Here’s a picture of it:</p>

<p><img src="/assets/Posts/2014/02/enterpad_application_desktop_english_e2.jpg" alt="Enterpad Keyboard" /></p>

<p>While macro keyboards/controllers are nothing new, there are a number of things that separate the Enterpad AHK keyboard from your typical macro keyboard:</p>

<ol>
  <li>The touch spots are not physical buttons; instead it uses a simple flat surface with 120 different positions that respond to touch. Think of it almost as a touch screen, but instead of having a screen to touch, you just touch a piece of paper.</li>
  <li>This leads to my next point, which is that you can use any overlay you want on the surface of Enterpad AHK keyboard; the overlay is just a piece of paper. The default overlay (piece of paper) that it ships with just has 120 squares on it, each labeled with their number (as shown in the picture above). Because the overlay is just a piece of paper, you can write (or draw) on it, allowing you to create custom labels for each of your 120 buttons; something that you can’t do with physical buttons. So what if you add or remap your macros after a month or a year? Just erase and re-write your labels (if you wrote them in pencil), or simply print off a new overlay. Also, you don’t need to have 120 different buttons; if you only require 12, then you could map 10 buttons to each one of the 12 commands you have, allowing for a larger touch spot to launch a specific script.</li>
  <li>It integrates directly with AHK. This means that you can easily write your macros/scripts in an awesome language that you (probably) already know. While you could technically have any old macro keyboard launch AHK scripts, it would mean mapping a keyboard shortcut for each script that you want to launch, which means cluttering up your keyboard shortcuts and potentially running them unintentionally. With the Enterpad AHK keyboard, AHK simply sees the 120 touch spots as an additional 120 keys on your keyboard, so you don’t have to clutter up your real keyboard’s hotkeys. Here is an example of a macro that displays a message box when the first touch spot is pressed:</li>
</ol>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="m">001</span><span class="p">:</span>
<span class="n">MsgBox</span><span class="p">,</span> <span class="s">"You pressed touch spot #1."</span>
<span class="n">Return</span>
</code></pre></div></div>

<h2 id="what-do-you-mean-when-you-say-use-it-to-launch-a-macro-or-script">What do you mean when you say use it to launch a macro or script?</h2>

<p>A macro or script is just a series of operations; basically they can be used to do ANYTHING that you can manually do on your computer. So some examples of things you can do are:</p>

<ul>
  <li>Open an application or file.</li>
  <li>Type specific text (such as your home address).</li>
  <li>Click on specific buttons or areas of a window.</li>
</ul>

<p>For example, you could have a script that opens Notepad, types “This text was written by an AHK script.”, saves the file to the desktop, and then closes Notepad. Macros are useful for automating things that you do repeatedly, such as visiting specific websites, entering usernames and passwords, typing out canned responses to emails, and much more.</p>

<p>The AHK community is very large and very active. You can find a script to do almost anything you want, and when you can’t (or if you need to customize an existing script) you are very likely to get answers to any questions that you post online. The Enterpad team also has <a href="http://cedeq.com/enterpad/en/autohotkey/useful-ahk-macros/">a bunch of general purpose scripts/examples available for you to use</a>, such having 10 custom clipboards, where button 1 copies to a custom clipboard, and button 11 pastes from it, button 2 copies to a different custom clipboard, and button 12 pastes from it, etc..</p>

<h2 id="why-would-i-want-the-enterpad-ahk-keyboard">Why would I want the Enterpad AHK Keyboard?</h2>

<p>If you are a fan of AutoHotkey and would like a separate physical device to launch your macros/scripts, the Enterpad AHK Keyboard is definitely a great choice. If you don’t want a separate physical device, be sure to check out <a href="http://ahkcommandpicker.codeplex.com">AHK Command Picker</a>, as it provides many of the same benefits without requiring a new piece of hardware.</p>

<h3 id="some-reasons-you-might-want-an-enterpad-ahk-keyboard">Some reasons you might want an Enterpad AHK Keyboard</h3>

<ul>
  <li>You use (or want to learn) AutoHotkey and prefer a separate physical device to launch your scripts.</li>
  <li>You want to be able to launch your scripts with a single button.</li>
  <li>You don’t want to clutter up your keyboard shortcuts.</li>
  <li>You want to be able to label all of your hotkeys.</li>
</ul>

<h3 id="some-reasons-you-may-want-a-different-macro-keyboard">Some reasons you may want a different macro keyboard</h3>

<ul>
  <li>It does not use physical buttons. This is great for some situations, but not for others. For example, if you are a gamer looking for a macro keyboard then you might prefer one with physical buttons so that you do not have to look away from the screen to be sure about which button you are pressing. Since the overlay is just a piece of paper though, you could perhaps do something like use little pieces of sticky-tac to mark certain buttons, so you could know which button your finger is on simply by feeling it.</li>
  <li>The price. At nearly $300 US, the Enterpad AHK keyboard is more expensive than many other macro keyboards. That said, those keyboards also don’t provide all of the benefits that the Enterpad AHK keyboard does.</li>
</ul>

<p>Even if you don’t want to use the Enterpad AHK keyboard yourself, you may want to get it for a friend or relative; especially if you have a very non-technical one. For example, you could hook it up to your grandma’s computer and write a AHK script that calls your computer via Skype, and then label a button (or 10 buttons to make it nice and big) on the Enterpad AHK keyboard so it is clear which button to press in order to call you.</p>

<p>One market that I think the Enterpad AHK keyboard could really be useful for is the corporate world, where you have many people doing the same job, and who all follow a set of instructions to do some processing. For example, at a call center where you have tens or hundreds of employees using the same software and performing the same job. One of their duties might be for placing new orders of a product for a caller, and this may involve clicking through 10 different menus or screens in order to get to the correct place to enter the customers information. This whole process could be automated to a single button press on the Enterpad AHK keyboard. You are probably thinking that the software should be redesigned to make the process of submitting orders less cumbersome, and you are right, but most companies don’t develop the software that they use, so they are at the mercy of the 3rd party software provider. In these cases, AHK can be a real time-saver, by a company deploying an Enterpad AHK keyboard to all of its staff with a custom labeled overlay, and the IT department writing the AHK scripts that the employees use with their Enterpad AHK keyboards, all of the staff can benefit from it without needing to know anything about AHK.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So should you go buy an Enterpad AHK Keyboard? That is really up to you. I have one, but find that I don’t use it very often because I tend to prefer to use the AHK Command Picker software so that my fingers never leave my keyboar0; Some of my co-workers have tried it out though and really love it, so if you prefer to have a separate physical device for launching your macros then the Enterpad AHK Keyboard might be perfect for you.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="AHK" /><category term="AutoHotkey" /><category term="Keyboard" /><category term="Macro" /><summary type="html"><![CDATA[If you follow my blog then you already know I’m a huge fan of AutoHotkey (AHK), and that I created the AHK Command Picker to allow me to have a limitless number of AHK macros quickly and easily accessible from my keyboard, without having a bunch of hotkeys (i.e. keyboard shortcuts) to remember. The team over at CEDEQ saw my blog posts and were kind enough to send me an Enterpad AHK Keyboard for free :-)]]></summary></entry><entry><title type="html">Don’t Write WPF Converters; Write C# Inline In Your XAML Instead Using QuickConverter</title><link href="https://blog.danskingdom.com/dont-write-wpf-converters-write-c-inline-in-your-xaml-instead-using-quickconverter/" rel="alternate" type="text/html" title="Don’t Write WPF Converters; Write C# Inline In Your XAML Instead Using QuickConverter" /><published>2013-12-13T21:09:10+00:00</published><updated>2013-12-13T21:09:10+00:00</updated><id>https://blog.danskingdom.com/dont-write-wpf-converters-write-c-inline-in-your-xaml-instead-using-quickconverter</id><content type="html" xml:base="https://blog.danskingdom.com/dont-write-wpf-converters-write-c-inline-in-your-xaml-instead-using-quickconverter/"><![CDATA[<p>If you’ve used binding at all in WPF then you more then likely have also written a converter. There are <a href="http://wpftutorial.net/ValueConverters.html">lots of</a> <a href="http://www.wpf-tutorial.com/data-binding/value-conversion-with-ivalueconverter/">tutorials on</a> <a href="http://www.codeproject.com/Articles/418271/Custom-Value-Conversion-in-WPF">creating converters</a>, so I’m not going to discuss that in length here. Instead I want to spread the word about <a href="https://quickconverter.codeplex.com/">a little known gem called QuickConverter</a>. QuickConverter is awesome because it allows you to write C# code directly in your XAML; this means no need for creating an explicit converter class. And <a href="http://www.nuget.org/packages/QuickConverter/">it’s available on NuGet</a> so it’s a snap to get it into your project.</p>

<h2 id="a-simple-inverse-boolean-converter-example">A simple inverse boolean converter example</h2>

<p>As a simple example, let’s do an inverse boolean converter; something that is so basic I’m surprised that it is still not included out of the box with Visual Studio (and why packages like <a href="https://wpfconverters.codeplex.com/">WPF Converters</a> exist). So basically if the property we are binding to is true, we want it to return false, and if it’s false, we want it to return true.</p>

<h3 id="the-traditional-approach">The traditional approach</h3>

<p><a href="http://www.codeproject.com/Articles/24330/WPF-Bind-to-Opposite-Boolean-Value-Using-a-Convert">This post shows the code</a> for how you would traditionally accomplish this. Basically you:</p>

<ol>
  <li>add a new file to your project to hold your new converter class,</li>
  <li>have the class implement IValueConverter,</li>
  <li>add the class as a resource in your xaml file, and then finally</li>
  <li>use it in the Converter property of the xaml control. Man, that is a lot of work to flip a bit!</li>
</ol>

<p>Just for reference, this is what step 4 might look like in the xaml:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="n">CheckBox</span> <span class="n">IsEnabled</span><span class="p">=</span><span class="s">"{Binding Path=ViewModel.SomeBooleanProperty, Converter={StaticResource InverseBooleanConverter}"</span> <span class="p">/&gt;</span>
</code></pre></div></div>

<h4 id="using-quickconverter">Using QuickConverter</h4>

<p>This is what you would do using QuickConverter:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="n">CheckBox</span> <span class="n">IsEnabled</span><span class="p">=</span><span class="s">"{qc:Binding '!$P', P={Binding Path=ViewModel.SomeBooleanProperty}}"</span> <span class="p">/&gt;</span>
</code></pre></div></div>

<p>That’s it! 1 step! How freaking cool is that! Basically we bind our SomeBooleanProperty to the variable $P, and then write our C# expressions against <code class="language-plaintext highlighter-rouge">$P</code>, all in xaml! This also allows us to skip steps 1, 2, and 3 of the traditional approach, allowing you to get more done.</p>

<h3 id="more-examples-using-quickconverter">More examples using QuickConverter</h3>

<p>The <a href="https://quickconverter.codeplex.com/documentation">QuickConverter documentation page</a> shows many more examples, such as a Visibility converter:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Visibility</span><span class="p">=</span><span class="s">"{qc:Binding '$P ? Visibility.Visible : Visibility.Collapsed', P={Binding ShowElement}}"</span>
</code></pre></div></div>

<p>Doing a null check:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IsEnabled</span><span class="p">=</span><span class="s">"{qc:Binding '$P != null', P={Binding Path=SomeProperty}"</span>
</code></pre></div></div>

<p>Checking a class instance’s property values:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IsEnabled</span><span class="p">=</span><span class="s">"{qc:Binding '$P.IsValid || $P.ForceAlways', P={Binding Path=SomeClassInstance}"</span>
</code></pre></div></div>

<p>Doing two-way binding:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Height</span><span class="p">=</span><span class="s">"{qc:Binding '$P * 10', ConvertBack='$value * 0.1', P={Binding TestWidth, Mode=TwoWay}}"</span>
</code></pre></div></div>

<p>Doing Multi-binding:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Angle</span><span class="p">=</span><span class="s">"{qc:MultiBinding 'Math.Atan2($P0, $P1) * 180 / 3.14159', P0={Binding ActualHeight, ElementName=rootElement}, P1={Binding ActualWidth, ElementName=rootElement}}"</span>
</code></pre></div></div>

<p>Declaring and using local variables in your converter expression:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">IsEnabled</span><span class="p">=</span><span class="s">"{qc:Binding '(Loc = $P.Value, A = $P.Show) =&gt; $Loc != null &amp;amp;&amp;amp; $A', P={Binding Obj}}"</span>
</code></pre></div></div>

<ul>
  <li>Note that the “&amp;&amp;” operator must be written as <code class="language-plaintext highlighter-rouge">&amp;amp;&amp;amp;</code> in XML.</li>
</ul>

<p>And there is even limited support for using lambdas, which allows LINQ to be used:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ItemsSource</span><span class="p">=</span><span class="s">"{qc:Binding '$P.Where(( (int)i ) =&gt; (bool)($i % 2 == 0))', P={Binding Source}}"</span>
</code></pre></div></div>

<h3 id="quick-converter-setup">Quick Converter Setup</h3>

<p>As mentioned above, <a href="https://quickconverter.codeplex.com/">Quick Converter</a> is <a href="http://www.nuget.org/packages/QuickConverter/">available via NuGet</a>. Once you have it installed in your project, there are 2 things you need to do:</p>

<h4 id="1-register-assemblies-for-the-types-that-you-plan-to-use-in-your-quick-converters">1. Register assemblies for the types that you plan to use in your quick converters</h4>

<p>For example, if you want to use the Visibility converter shown above, you need to register the System.Windows assembly, since that is where the System.Windows.Visibility enum being referenced lives. You can register the System.Windows assembly with QuickConverter using this line:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">QuickConverter</span><span class="p">.</span><span class="n">EquationTokenizer</span><span class="p">.</span><span class="nf">AddNamespace</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">Windows</span><span class="p">.</span><span class="n">Visibility</span><span class="p">));</span>
</code></pre></div></div>

<p>In order to avoid a XamlParseException at run-time, this line needs to be executed before the quick converter executes. To make this easy, I just register all of the assemblies with QuickConverter in my application’s constructor. That way I know they have been registered before any quick converter expressions are evaluated.</p>

<p>So my App.xaml.cs file contains this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">App</span> <span class="p">:</span> <span class="n">Application</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="nf">App</span><span class="p">()</span> <span class="p">:</span> <span class="k">base</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// Setup Quick Converter.</span>
        <span class="n">QuickConverter</span><span class="p">.</span><span class="n">EquationTokenizer</span><span class="p">.</span><span class="nf">AddNamespace</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="kt">object</span><span class="p">));</span>
        <span class="n">QuickConverter</span><span class="p">.</span><span class="n">EquationTokenizer</span><span class="p">.</span><span class="nf">AddNamespace</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">Windows</span><span class="p">.</span><span class="n">Visibility</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here I also registered the System assembly (using “typeof(object)”) in order to make the primitive types (like bool) available.</p>

<h4 id="2-add-the-quickconverter-namespace-to-your-xaml-files">2. Add the QuickConverter namespace to your Xaml files</h4>

<p>As with all controls in xaml, before you can use a you a control you must create a reference to the namespace that the control is in. So to be able to access and use QuickConverter in your xaml file, you must include it’s namespace, which can be done using:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">xmlns</span><span class="p">:</span><span class="n">qc</span><span class="p">=</span><span class="s">"clr-namespace:QuickConverter;assembly=QuickConverter"</span>
</code></pre></div></div>

<h3 id="so-should-i-go-delete-all-my-existing-converters">So should I go delete all my existing converters?</h3>

<p>As crazy awesome as QuickConverter is, it’s not a complete replacement for converters. Here are a few scenarios where you would likely want to stick with traditional converters:</p>

<ol>
  <li>You need some very complex logic that is simply easier to write using a traditional converter. For example, we have some converters that access our application cache and lock resources and do a lot of other logic, where it would be tough (impossible?) to write all of that logic inline with QuickConverter. Also, by writing it using the traditional approach you get things like VS intellisense and compile-time error checking.</li>
  <li>If the converter logic that you are writing is very complex, you may want it enclosed in a converter class to make it more easily reusable; this allows for a single reusable object and avoids copy-pasting complex logic all over the place. Perhaps the first time you write it you might do it as a QuickConverter, but if you find yourself copy-pasting that complex logic a lot, move it into a traditional converter.</li>
  <li>If you need to debug your converter, that can’t be done with QuickConverter (yet?).</li>
</ol>

<h3 id="summary">Summary</h3>

<p>So QuickConverter is super useful and can help speed up development time by allowing most, if not all, of your converters to be written inline. In my experience 95% of converters are doing very simple things (null checks, to strings, adapting one value type to another, etc.), which are easy to implement inline. This means fewer files and classes cluttering up your projects. If you need to do complex logic or debug your converters though, then you may want to use traditional converters for those few cases.</p>

<p>So, writing C# inline in your xaml; how cool is that! I can’t believe Microsoft didn’t think of and implement this. One of the hardest things to believe is that Johannes Moersch came up with this idea and implemented it while on a co-op work term in my office! A CO-OP STUDENT WROTE QUICKCONVERTER! Obviously Johannes is a very smart guy, and he’s no longer a co-op student; he’ll be finishing up his bachelor’s degree in the coming months.</p>

<p>I hope you find QuickConverter as helpful as I have, and if you have any suggestions for improvements, <a href="https://quickconverter.codeplex.com/discussions">be sure to leave Johannes a comment on the CodePlex page</a>.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="CSharp" /><category term="WPF" /><category term="XAML" /><category term="CSharp" /><category term="Converter" /><category term="CSharp" /><category term="Inline" /><category term="Quick" /><category term="QuickConverter" /><category term="WPF" /><category term="XAML" /><summary type="html"><![CDATA[If you’ve used binding at all in WPF then you more then likely have also written a converter. There are lots of tutorials on creating converters, so I’m not going to discuss that in length here. Instead I want to spread the word about a little known gem called QuickConverter. QuickConverter is awesome because it allows you to write C# code directly in your XAML; this means no need for creating an explicit converter class. And it’s available on NuGet so it’s a snap to get it into your project.]]></summary></entry><entry><title type="html">Get AutoHotkey To Interact With Admin Windows Without Running AHK Script As Admin</title><link href="https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin/" rel="alternate" type="text/html" title="Get AutoHotkey To Interact With Admin Windows Without Running AHK Script As Admin" /><published>2013-11-21T17:22:37+00:00</published><updated>2023-03-04T00:00:00+00:00</updated><id>https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin</id><content type="html" xml:base="https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin/"><![CDATA[<blockquote>
  <p>March 2023 Update: I’ve found another solution that I prefer over this one. See <a href="https://blog.danskingdom.com/Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/">this post</a> for details.</p>
</blockquote>

<p>A while back I posted about <a href="https://blog.danskingdom.com/autohotkey-cannot-interact-with-windows-8-windowsor-can-it/">AutoHotkey not being able to interact with Windows 8 windows and other applications that were Ran As Admin</a>. My solution was to run your <a href="http://www.autohotkey.com/">AutoHotkey</a> (AHK) script as admin as well, and I also showed how to have your AHK script start automatically with Windows, but not as an admin. Afterwards I followed that up with a post about how to <a href="https://blog.danskingdom.com/get-autohotkey-script-to-run-as-admin-at-startup/">get your AHK script to run as admin on startup</a>, so life was much better, but still not perfect.</p>

<h2 id="problems-with-running-your-ahk-script-as-admin">Problems with running your AHK script as admin</h2>

<ol>
  <li>You may have to deal with the annoying UAC prompt every time you launch your script.</li>
  <li>Any programs the script launches also receive administrative privileges.</li>
</ol>

<p>#1 is only a problem if you haven’t set your AHK script to run as admin on startup as I showed in <a href="https://blog.danskingdom.com/get-autohotkey-script-to-run-as-admin-at-startup/">my other blog post</a> (i.e. you are still manually launching your script) or you haven’t changed your UAC settings to never prompt you with notifications (which some companies restrict) (see screenshot).</p>

<p><img src="/assets/Posts/2013/11/UAC-Never-Notify1.png" alt="UAC Never Notify" /></p>

<p>#2 was a problem for me. I use <a href="http://ahkcommandpicker.codeplex.com/">AHK Command Picker</a> every day. A lot. I’m a developer and in order for Visual Studio to interact with IIS it requires admin privileges, which meant that if I wanted to be able to use AHK Command Picker in Visual Studio, I had to run it as admin as well. The problem for me was that I use AHK Command Picker to launch almost all of my applications, which meant that most of my apps were now also running as an administrator. For the most part this was fine, but there were a couple programs that gave me problems running as admin. E.g. With PowerShell ISE when I double clicked on a PowerShell file to edit it, instead of opening in the current (admin) ISE instance, it would open a new ISE instance.</p>

<h2 id="there-is-a-solution">There is a solution</h2>

<p>Today I stumbled across <a href="http://www.autohotkey.com/board/topic/70449-enable-interaction-with-administrative-programs/">this post</a> on the AHK community forums. Lexikos has provided an AHK script that will digitally sign the AutoHotkey executable, allowing it to interact with applications running as admin, even when your AHK script isn’t.</p>

<blockquote>
  <p>March 2023 Update: I’ve found that this solution no longer works.
I’m not certain if the problem is something with my local machine, or an update to newer versions of AutoHotkey.
If you don’t want to use <a href="https://blog.danskingdom.com/Prevent-Admin-apps-from-blocking-AutoHotkey-by-using-UI-Access/">my new preferred solution</a>, let me know if this one still works for you in the comments.</p>
</blockquote>

<p>Running his script is pretty straight forward:</p>

<ol>
  <li>Download and unzip his EnableUIAccess.zip file; you can also <a href="/assets/Posts/2014/06/EnableUIAccess2.zip">get it here</a>.</li>
  <li>Double-click the EnableUIAccess.ahk script to run it, and it will automatically prompt you.</li>
  <li>Read the disclaimer and click OK.</li>
  <li>On the <strong>Select Source File</strong> prompt choose the C:\Program Files\AutoHotkey\AutoHotkey.exe file. This was already selected by default for me. (Might be Program Files (x86) if you have 32-bit AHK installed on 64-bit Windows)</li>
  <li>On the <strong>Select Destination File</strong> prompt choose the same C:\Program Files\AutoHotkey\AutoHotkey.exe file again. Again, this was already selected by default for me.</li>
  <li>Click Yes to replace the existing file.</li>
  <li>Click Yes when prompted to Run With UI Access.</li>
</ol>

<p>That’s it. (Re)Start your AHK scripts and they should now be able to interact with Windows 8 windows and applications running as admin :-)</p>

<p>This is a great solution if you want your AHK script to interact with admin windows, but don’t want to run your script as an admin.</p>

<h2 id="did-you-know">Did you know</h2>

<p>If you do want to launch an application as admin, but don’t want to run your AHK script as admin, you can use the <a href="http://www.autohotkey.com/docs/commands/RunAs.htm">RunAs</a> command.</p>

<p>I hope you found this article useful. Feel free to leave a comment.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="Windows" /><category term="Admin" /><category term="Administrator" /><category term="AHK" /><category term="Application" /><category term="AutoHotkey" /><category term="Run As Admin" /><category term="UAC" /><category term="Windows" /><summary type="html"><![CDATA[March 2023 Update: I’ve found another solution that I prefer over this one. See this post for details.]]></summary></entry><entry><title type="html">Provide A Batch File To Run Your PowerShell Script From; Your Users Will Love You For It</title><link href="https://blog.danskingdom.com/allow-others-to-run-your-powershell-scripts-from-a-batch-file-they-will-love-you-for-it/" rel="alternate" type="text/html" title="Provide A Batch File To Run Your PowerShell Script From; Your Users Will Love You For It" /><published>2013-11-17T05:16:07+00:00</published><updated>2013-11-17T05:16:07+00:00</updated><id>https://blog.danskingdom.com/allow-others-to-run-your-powershell-scripts-from-a-batch-file-they-will-love-you-for-it</id><content type="html" xml:base="https://blog.danskingdom.com/allow-others-to-run-your-powershell-scripts-from-a-batch-file-they-will-love-you-for-it/"><![CDATA[<p>Aside - This post has received many tangential questions in the comments. Your best bet at getting an answer to those questions is to check <a href="https://stackoverflow.com">Stack Overflow</a> and/or post your question there.</p>

<p>A while ago in <a href="https://blog.danskingdom.com/getting-custom-tfs-checkin-policies-to-work-when-committing-from-the-command-line-i-e-tf-checkin/">one of my older posts</a> I included a little gem that I think deserves it’s own dedicated post; calling PowerShell scripts from a batch file.</p>

<h2 id="why-call-my-powershell-script-from-a-batch-file">Why call my PowerShell script from a batch file?</h2>

<p>When I am writing a script for other people to use (in my organization, or for the general public) or even for myself sometimes, I will often include a simple batch file (i.e. *.bat or *.cmd file) that just simply calls my PowerShell script and then exits. I do this because even though PowerShell is awesome, not everybody knows what it is or how to use it; non-technical folks obviously, but even many of the technical folks in our organization have never used PowerShell.</p>

<p>Let’s list the problems with sending somebody the PowerShell script alone; The first two points below are hurdles that <strong>every</strong> user stumbles over the first time they encounter PowerShell (they are there for security purposes):</p>

<ol>
  <li>When you double-click a PowerShell script (*.ps1 file) the default action is often to open it up in an editor, not to run it (<a href="https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces/">you can change this for your PC</a>).</li>
  <li>When you do figure out you need to right-click the .ps1 file and choose Open With –&gt; Windows PowerShell to run the script, it will fail with a warning saying that the execution policy is currently configured to not allow scripts to be ran.</li>
  <li>My script may require admin privileges in order to run correctly, and it can be tricky to run a PowerShell script as admin without going into a PowerShell console and running the script from there, which a lot of people won’t know how to do.</li>
  <li>A potential problem that could affect PowerShell Pros is that it’s possible for them to have variables or other settings set in their PowerShell profile that could cause my script to not perform correctly; this is pretty unlikely, but still a possibility.</li>
</ol>

<p>So imagine you’ve written a PowerShell script that you want your grandma to run (or an HR employee, or an executive, or your teenage daughter, etc.). Do you think they’re going to be able to do it? Maybe, maybe not.</p>

<p><em>You should be kind to your users and provide a batch file to call your PowerShell script</em>.</p>

<p>The beauty of batch file scripts is that by default the script is ran when it is double-clicked (solves problem #1), and all of the other problems can be overcome by using a few arguments in our batch file.</p>

<h2 id="ok-i-see-your-point-so-how-do-i-call-my-powershell-script-from-a-batch-file">Ok, I see your point. So how do I call my PowerShell script from a batch file?</h2>

<p>First, <em>the code I provide assumes that the batch file and PowerShell script are in the same directory</em>. So if you have a PowerShell script called “MyPowerShellScript.ps1” and a batch file called “RunMyPowerShellScript.cmd”, this is what the batch file would contain:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@ECHO OFF
SET <span class="nv">ThisScriptsDirectory</span><span class="o">=</span>%~dp0
SET <span class="nv">PowerShellScriptPath</span><span class="o">=</span>%ThisScriptsDirectory%MyPowerShellScript.ps1
PowerShell <span class="nt">-NoProfile</span> <span class="nt">-ExecutionPolicy</span> Bypass <span class="nt">-Command</span> <span class="s2">"&amp; '%PowerShellScriptPath%'"</span><span class="p">;</span>
</code></pre></div></div>

<p>Line 1 just prevents the contents of the batch file from being printed to the command prompt (so it’s optional). Line 2 gets the directory that the batch file is in. Line 3 just appends the PowerShell script filename to the script directory to get the full path to the PowerShell script file, so this is the only line you would need to modify; <em>replace MyPowerShellScript.ps1 with your PowerShell script’s filename</em>. The 4th line is the one that actually calls the PowerShell script and contains the magic.</p>

<p>The <strong>–NoProfile</strong> switch solves problem #4 above, and the <strong>–ExecutionPolicy Bypass</strong> argument solves problem #2. But that still leaves problem #3 above, right?</p>

<h3 id="call-your-powershell-script-from-a-batch-file-with-administrative-permissions-ie-run-as-admin">Call your PowerShell script from a batch file with Administrative permissions (i.e. Run As Admin)</h3>

<p>If your PowerShell script needs to be run as an admin for whatever reason, the 4th line of the batch file will need to change a bit:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@ECHO OFF
SET <span class="nv">ThisScriptsDirectory</span><span class="o">=</span>%~dp0
SET <span class="nv">PowerShellScriptPath</span><span class="o">=</span>%ThisScriptsDirectory%MyPowerShellScript.ps1
PowerShell <span class="nt">-NoProfile</span> <span class="nt">-ExecutionPolicy</span> Bypass <span class="nt">-Command</span> <span class="s2">"&amp; {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""%PowerShellScriptPath%""' -Verb RunAs}"</span><span class="p">;</span>
</code></pre></div></div>

<p>We can’t call the PowerShell script as admin from the command prompt, but we can from PowerShell; so we essentially start a new PowerShell session, and then have that session call the PowerShell script using the <strong>–Verb RunAs</strong> argument to specify that the script should be run as an administrator.</p>

<p>And voila, that’s it. Now all anybody has to do to run your PowerShell script is double-click the batch file; something that even your grandma can do (well, hopefully). So will your users really love you for this; well, no. Instead they just won’t be cursing you for sending them a script that they can’t figure out how to run. It’s one of those things that nobody notices until it doesn’t work.</p>

<p>So take the extra 10 seconds to create a batch file and copy/paste the above text into it; it’ll save you time in the long run when you don’t have to repeat to all your users the specific instructions they need to follow to run your PowerShell script.</p>

<p><em>I typically use this trick for myself too when my script requires admin rights</em>, as it just makes running the script faster and easier.</p>

<h2 id="bonus">Bonus</h2>

<p>One more tidbit that I often include at the end of my PowerShell scripts is the following code:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># If running in the console, wait for input before closing.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$Host</span><span class="o">.</span><span class="nf">Name</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"ConsoleHost"</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Press any key to continue..."</span><span class="w">
    </span><span class="bp">$Host</span><span class="o">.</span><span class="nf">UI</span><span class="o">.</span><span class="nf">RawUI</span><span class="o">.</span><span class="nf">FlushInputBuffer</span><span class="p">()</span><span class="w">   </span><span class="c"># Make sure buffered input doesn't "press a key" and skip the ReadKey().</span><span class="w">
    </span><span class="bp">$Host</span><span class="o">.</span><span class="nf">UI</span><span class="o">.</span><span class="nf">RawUI</span><span class="o">.</span><span class="nf">ReadKey</span><span class="p">(</span><span class="s2">"NoEcho,IncludeKeyUp"</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This will prompt the user for keyboard input before closing the PowerShell console window. This is useful because it allows users to read any errors that your PowerShell script may have thrown before the window closes, or even just so they can see the “Everything completed successfully” message that your script spits out so they know that it ran correctly. Related side note: you can <a href="https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces/">change your PC to always leave the PowerShell console window open</a> after running a script, if that is your preference.</p>

<p>I hope you find this useful. Feel free to leave comments.</p>

<p>Happy coding!</p>

<h2 id="update">Update</h2>

<p>Several people have left comments asking how to pass parameters into the PowerShell script from the batch file.</p>

<p>Here is how to pass in ordered parameters:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PowerShell <span class="nt">-NoProfile</span> <span class="nt">-ExecutionPolicy</span> Bypass <span class="nt">-Command</span> <span class="s2">"&amp; '%PowerShellScriptPath%' 'First Param Value' 'Second Param Value'"</span><span class="p">;</span>
</code></pre></div></div>

<p>And here is how to pass in named parameters:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PowerShell <span class="nt">-NoProfile</span> <span class="nt">-ExecutionPolicy</span> Bypass <span class="nt">-Command</span> <span class="s2">"&amp; '%PowerShellScriptPath%' -Param1Name 'Param 1 Value' -Param2Name 'Param 2 Value'"</span>
</code></pre></div></div>

<p>And if you are running the admin version of the script, here is how to pass in ordered parameters:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PowerShell <span class="nt">-NoProfile</span> <span class="nt">-ExecutionPolicy</span> Bypass <span class="nt">-Command</span> <span class="s2">"&amp; {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File """"%PowerShellScriptPath%"""" """"First Param Value"""" """"Second Param Value"""" ' -Verb RunAs}"</span>
</code></pre></div></div>

<p>And here is how to pass in named parameters:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PowerShell <span class="nt">-NoProfile</span> <span class="nt">-ExecutionPolicy</span> Bypass <span class="nt">-Command</span> <span class="s2">"&amp; {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File """"%PowerShellScriptPath%"""" -Param1Name """"Param 1 Value"""" -Param2Name """"Param 2 value"""" ' -Verb RunAs}"</span><span class="p">;</span>
</code></pre></div></div>

<p>And yes, the PowerShell script name and parameters need to be wrapped in 4 double quotes in order to properly handle paths/values with spaces.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term=".bat" /><category term=".cmd" /><category term=".ps1" /><category term="batch file" /><category term="Execution Policy" /><category term="PowerShell" /><category term="Profile" /><category term="Run As Admin" /><summary type="html"><![CDATA[Aside - This post has received many tangential questions in the comments. Your best bet at getting an answer to those questions is to check Stack Overflow and/or post your question there.]]></summary></entry><entry><title type="html">Problems Caused By Installing Windows 8.1 Update</title><link href="https://blog.danskingdom.com/problems-caused-by-installing-windows-8-1-update/" rel="alternate" type="text/html" title="Problems Caused By Installing Windows 8.1 Update" /><published>2013-11-08T21:37:20+00:00</published><updated>2013-11-08T21:37:20+00:00</updated><id>https://blog.danskingdom.com/problems-caused-by-installing-windows-8-1-update</id><content type="html" xml:base="https://blog.danskingdom.com/problems-caused-by-installing-windows-8-1-update/"><![CDATA[<p>Myself and a few co-workers have updated from Windows 8 to Windows 8.1 and have run into some weird problems. After a bit of Googling I have found that we are not alone. This is just a quick list of some things the the Windows 8.1 Update seems to have broken. I’ll update this post as I find more issues.</p>

<h2 id="ie-11-breaks-some-websites">IE 11 breaks some websites</h2>

<ul>
  <li>I found that some of the links in the website our office uploads our Escrow deposits to no longer worked in IE 11 (which 8.1 installs). Turning on the developer tools showed that it was throwing a Javascript error about an undefined function. Everything works fine in IE 10 though and no undefined errors are thrown.</li>
  <li>I have also noticed that after doing a search on Google and clicking one of the links, in order to get back to the Google results page you have to click the Back button twice; the first Back-click just takes you to a blank page (when you click the Google link it directs you to an empty page, which then forwards you to the correct page).</li>
  <li><a href="http://answers.microsoft.com/en-us/ie/forum/ie11_pr-windows8_1_pr/windows-81-upgrade-ie-11-not-working-properly/87224e09-2732-48c6-823d-c2099faead48">Others have complained</a> that they are experiencing problems with GMail and Silverlight after the 8.1 update.</li>
</ul>

<p>So it may just be that IE 11 updated it’s standards to be more compliant and now many websites don’t meet the new requirements (I’m not sure); but either way, you may find that some of your favorite websites no longer work properly with IE 11, and you’ll have to wait for IE 11 or the website to make an update.</p>

<h2 id="vpn-stopped-working">VPN stopped working</h2>

<p>We use the SonicWall VPN client at my office, and I found that it no longer worked after updating to Windows 8.1. The solution was a simple uninstall, reinstall, but still, it’s just one more issue to add to the list.</p>

<h2 id="more">More?</h2>

<p>Have you noticed other things broken after doing the Windows 8.1 update? Share them in the comments below!</p>

<p>In my personal opinion, I would wait a while longer before updating to Windows 8.1; give Microsoft more time to fix some of these issues. Many of the new features in Windows 8.1 aren’t even noticeable yet, as many apps don’t yet take advantage of them. Also, while MS did put a Start button back in, it’s not nearly as powerful as the Windows 7 Start button, so if that’s your reason for upgrading to 8.1 just go get <a href="http://www.classicshell.net/">Classic Shell</a> instead.</p>

<p>Hopefully Microsoft will be releasing hotfixes to get these issues addressed sooner than later.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows" /><category term="Windows 8" /><category term="8.1" /><category term="Broken" /><category term="Problem" /><category term="Windows" /><category term="Windows 8" /><category term="Windows 8.1" /><summary type="html"><![CDATA[Myself and a few co-workers have updated from Windows 8 to Windows 8.1 and have run into some weird problems. After a bit of Googling I have found that we are not alone. This is just a quick list of some things the the Windows 8.1 Update seems to have broken. I’ll update this post as I find more issues.]]></summary></entry><entry><title type="html">Always Explicitly Set Your Parameter Set Variables For PowerShell v2.0 Compatibility</title><link href="https://blog.danskingdom.com/always-explicitly-set-your-parameter-set-variables-for-powershell-v2-0-compatibility/" rel="alternate" type="text/html" title="Always Explicitly Set Your Parameter Set Variables For PowerShell v2.0 Compatibility" /><published>2013-10-28T17:25:32+00:00</published><updated>2013-10-28T17:25:32+00:00</updated><id>https://blog.danskingdom.com/always-explicitly-set-your-parameter-set-variables-for-powershell-v2-0-compatibility</id><content type="html" xml:base="https://blog.danskingdom.com/always-explicitly-set-your-parameter-set-variables-for-powershell-v2-0-compatibility/"><![CDATA[<h2 id="what-are-parameter-sets-anyways">What are parameter sets anyways?</h2>

<p><a href="http://msdn.microsoft.com/en-us/library/windows/desktop/dd878348%28v=vs.85%29.aspx">Parameter sets</a> were introduced in PowerShell v2.0 and are useful for enforcing mutually exclusive parameters on a cmdlet. <a href="http://blogs.technet.com/b/heyscriptingguy/archive/2011/06/30/use-parameter-sets-to-simplify-powershell-commands.aspx">Ed Wilson has a good little article</a> explaining what parameter sets are and how to use them. Essentially they allow us to write a single cmdlet that might otherwise have to be written as 2 or more cmdlets that took different parameters. For example, instead of having to create Process-InfoFromUser, Process-InfoFromFile, and Process-InfoFromUrl cmdlets, we could create a single Process-Info cmdlet that has 3 mutually exclusive parameters, [switch]$PromptUser, [string]$FilePath, and [string]$Url. If the cmdlet is called with more than one of these parameters, it throws an error.</p>

<p>You could just be lazy and not use parameter sets and allow all 3 parameters to be specified and then just use the first one, but the user won’t know which one of the 3 they provided will be used; they might assume that all 3 will be used. This would also force the user to have to read the documentation (assuming you have provided it). Using parameter sets enforces makes it clear to the user which parameters are able to be used with other parameters. Also, most PowerShell editors process parameter sets to have the intellisense properly show the parameters that can be used with each other.</p>

<h2 id="ok-parameter-sets-sound-awesome-i-want-to-use-them-whats-the-problem">Ok, parameter sets sound awesome, I want to use them! What’s the problem?</h2>

<p>The problem I ran into was in my <a href="https://invokemsbuild.codeplex.com/">Invoke-MsBuild module that I put on CodePlex</a>, I had a [switch]$PassThru parameter that was part of a parameter set. Within the module I had:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$PassThru</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">do</span><span class="w"> </span><span class="n">something...</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">do</span><span class="w"> </span><span class="n">something</span><span class="w"> </span><span class="nx">else...</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This worked great for me during my testing since I was using PowerShell v3.0. The problem arose once I released my code to the public; I received an issue from a user who was getting the following error message:</p>

<blockquote>
  <p>Invoke-MsBuild : Unexpected error occurred while building “[path]\my.csproj”: The variable ‘$PassThru’ cannot be retrieved because it has not been set.</p>

  <p>At build.ps1:84 char:25</p>

  <ul>
    <li>$result = Invoke-MsBuild «« -Path “[path]\my.csproj” -BuildLogDirectoryPath “$scriptPath” -Params “/property:Configuration=Release”</li>
  </ul>
</blockquote>

<p>After some investigation I determined the problem was that they were using PowerShell v2.0, and that my script uses Strict Mode. I use <strong>Set-StrictMode -Version Latest</strong> in all of my scripts to help me catch any syntax related errors and to make sure my scripts will in fact do what I intend them to do. While you could simply not use strict mode and <strong>you</strong> wouldn’t have a problem, I don’t recommend that; if others are going to call your cmdlet (or you call it from a different script), there’s a good chance they may have Strict Mode turned on and your cmdlet may break for them.</p>

<h2 id="so-should-i-not-use-parameter-sets-with-powershell-v20-is-there-a-fix">So should I not use parameter sets with PowerShell v2.0? Is there a fix?</h2>

<p>You absolutely SHOULD use parameter sets whenever you can and it makes sense, and yes there is a fix. If you require your script to run on PowerShell v2.0, there is just one extra step you need to take, which is to explicitly set the values for any parameters that use a parameter set <a href="http://stackoverflow.com/questions/3159949/in-powershell-how-do-i-test-whether-or-not-a-specific-variable-exists-in-global">and don’t exist</a>. Luckily we can use the <strong>Test-Path</strong> cmdlet to test if a variable has been defined in a specific scope or not.</p>

<p>Here is an example of how to detect if a variable is not defined in the Private scope and set its default value. We specify the scope in case a variable with the same name exists outside of the cmdlet in the global scope or an inherited scope.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Default the ParameterSet variables that may not have been set depending on which parameter set is being used. This is required for PowerShell v2.0 compatibility.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nx">Variable:Private:SomeStringParameter</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$SomeStringParameter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nx">Variable:Private:SomeIntegerParameter</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$SomeIntegerParameter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nx">Variable:Private:SomeSwitchParameter</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$SomeSwitchParameter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>If you prefer, instead of setting a default value for the parameter you could just check if it is defined first when using it in your script. I like this approach however, because I can put this code right after my cmdlet parameters so I’m modifying all of my parameter set properties in one place, and I don’t have to remember to check if the variable is defined later when writing the body of my cmdlet; otherwise I’m likely to forget to do the “is defined” check, and will likely miss the problem since I do most of my testing in PowerShell v3.0.</p>

<p>Another approach rather than checking if a parameter is defined or not, is to <a href="http://blogs.msdn.com/b/powershell/archive/2008/12/23/powershell-v2-parametersets.aspx">check which Parameter Set Name is being used</a>; this will implicitly let you know which parameters are defined.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">switch</span><span class="w"> </span><span class="p">(</span><span class="nv">$PsCmdlet</span><span class="o">.</span><span class="nf">ParameterSetName</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="s2">"SomeParameterSetName"</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You supplied the Some variable."</span><span class="p">;</span><span class="w"> </span><span class="kr">break</span><span class="p">}</span><span class="w">
    </span><span class="s2">"OtherParameterSetName"</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You supplied the Other variable."</span><span class="p">;</span><span class="w"> </span><span class="kr">break</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>I still prefer to default all of my parameters, but you may prefer this method.</p>

<p>I hope you find this useful. Check out my other <a href="https://blog.danskingdom.com/powershell-2-0-vs-3-0-syntax-differences-and-more/">article for more PowerShell v2.0 vs. v3.0 differences</a>.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Compatible" /><category term="parameter" /><category term="Parameter Set" /><category term="PowerShell" /><category term="Variable" /><category term="Version" /><summary type="html"><![CDATA[What are parameter sets anyways?]]></summary></entry><entry><title type="html">PowerShell Code To Ensure Client Is Using At Least The Minimum Required PowerShell Version</title><link href="https://blog.danskingdom.com/ensure-client-is-using-at-least-the-minimum-required-powershell-version/" rel="alternate" type="text/html" title="PowerShell Code To Ensure Client Is Using At Least The Minimum Required PowerShell Version" /><published>2013-10-25T17:48:31+00:00</published><updated>2013-10-25T17:48:31+00:00</updated><id>https://blog.danskingdom.com/ensure-client-is-using-at-least-the-minimum-required-powershell-version</id><content type="html" xml:base="https://blog.danskingdom.com/ensure-client-is-using-at-least-the-minimum-required-powershell-version/"><![CDATA[<p>Here’s some simple code that will throw an exception if the client running your script is not using the version of PowerShell (or greater) that is required; just change the <code class="language-plaintext highlighter-rouge">$REQUIRED_POWERSHELL_VERSION</code> variable value to the minimum version that the script requires.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Throw an exception if client is not using the minimum required PowerShell version.</span><span class="w">
</span><span class="nv">$REQUIRED_POWERSHELL_VERSION</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">3.0</span><span class="w">  </span><span class="c"># The minimum Major.Minor PowerShell version that is required for the script to run.</span><span class="w">
</span><span class="nv">$POWERSHELL_VERSION</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$PSVersionTable</span><span class="o">.</span><span class="nf">PSVersion</span><span class="o">.</span><span class="nf">Major</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="bp">$PSVersionTable</span><span class="o">.</span><span class="nf">PSVersion</span><span class="o">.</span><span class="nf">Minor</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">10</span><span class="p">)</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$REQUIRED_POWERSHELL_VERSION</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="nv">$POWERSHELL_VERSION</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"PowerShell version </span><span class="nv">$REQUIRED_POWERSHELL_VERSION</span><span class="s2"> is required for this script; You are only running version </span><span class="nv">$POWERSHELL_VERSION</span><span class="s2">. Please update PowerShell to at least version </span><span class="nv">$REQUIRED_POWERSHELL_VERSION</span><span class="s2">."</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="update-">UPDATE {</h3>

<p>Thanks to Robin M for pointing out that PowerShell has <a href="http://technet.microsoft.com/en-us/library/hh847765.aspx">the built-in #Requires statement</a> for this purpose, so you do not need to use the code above. Instead, simply place the following code anywhere in your script to enforce the desired PowerShell version required to run the script:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#Requires -Version 3.0</span><span class="w">
</span></code></pre></div></div>

<p>If the user does not have the minimum required version of PowerShell installed, they will see an error message like this:</p>

<blockquote>
  <p>The script ‘foo.ps1’ cannot be run because it contained a “#requires” statement at line 1 for Windows PowerShell version 3.0 which is incompatible with the installed Windows PowerShell version of 2.0.</p>
</blockquote>

<h3 id="-update">} Update</h3>

<p>So if your script requires, for example, PowerShell v3.0, just put this at the start of your script to have it error out right away with a meaningful error message; otherwise your script may throw other errors that mask the real issue, potentially leading the user to spend many hours troubleshooting your script, or to give up on it all together.</p>

<p>I’ve been bitten by this in the past a few times now, where people report issues on my CodePlex scripts where the error message seems ambiguous. So now any scripts that I release to the general public will have this check in it to give them a proper error message. I have also created a page on <a href="https://blog.danskingdom.com/powershell-2-0-vs-3-0-syntax-differences-and-more/">PowerShell v2 vs. v3 differences</a> that I’m going to use to keep track of the differences that I encounter, so that I can have confidence in the minimum powershell version that I set on my scripts. I also plan on creating a v3 vs. v4 page once I start using PS v4 features more. Of course, the best test is to actually run your script in the minimum powershell version that you set, which I mention how to do on my PS v2 vs. v3 page.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Minimum" /><category term="PowerShell" /><category term="Required" /><category term="Version" /><summary type="html"><![CDATA[Here’s some simple code that will throw an exception if the client running your script is not using the version of PowerShell (or greater) that is required; just change the $REQUIRED_POWERSHELL_VERSION variable value to the minimum version that the script requires.]]></summary></entry><entry><title type="html">PowerShell Script To Get Path Lengths</title><link href="https://blog.danskingdom.com/powershell-script-to-check-path-lengths/" rel="alternate" type="text/html" title="PowerShell Script To Get Path Lengths" /><published>2013-10-24T23:14:59+00:00</published><updated>2020-08-07T00:00:00+00:00</updated><id>https://blog.danskingdom.com/powershell-script-to-check-path-lengths</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-script-to-check-path-lengths/"><![CDATA[<p>A while ago I created a <a href="https://pathlengthchecker.codeplex.com/">Path Length Checker</a> tool in C# that has a “nice” GUI, and put it up on CodePlex. One of the users reported that he was trying to use it to scan his entire C: drive, but that it was crashing. Turns out that the <code class="language-plaintext highlighter-rouge">System.IO.Directory.GetFileSystemEntries()</code> call was throwing a permissions exception when trying to access the “C:\Documents and Settings” directory. Even when running the app as admin it throws this exception. In the meantime while I am working on implementing a workaround for the app, I wrote up a quick PowerShell script that the user could use to get all of the path lengths. That is what I present to you here.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Output the length of all files and folders in the given directory path.</span><span class="w">
</span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
</span><span class="kr">param</span><span class="w">
</span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'The directory to scan path lengths in. Subdirectories will be scanned as well.'</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$DirectoryPathToScan</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\Temp'</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Only paths this length or longer will be included in the results. Set this to 260 to find problematic paths in Windows.'</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w"> </span><span class="nv">$MinimumPathLengthsToShow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'If the results should be written to the console or not. Can be slow if there are many results.'</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="w"> </span><span class="nv">$WriteResultsToConsole</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'If the results should be shown in a Grid View or not once the scanning completes.'</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="w"> </span><span class="nv">$WriteResultsToGridView</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'If the results should be written to a file or not.'</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="w"> </span><span class="nv">$WriteResultsToFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w">

    </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">HelpMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'The file path to write the results to when $WriteResultsToFile is true.'</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$ResultsFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'C:\Temp\PathLengths.txt'</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c"># Ensure output directory exists</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$resultsFileDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nv">$ResultsFilePath</span><span class="w"> </span><span class="nt">-Parent</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$resultsFileDirectoryPath</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">New-Item</span><span class="w"> </span><span class="nv">$resultsFileDirectoryPath</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="c"># Open a new file stream (nice and fast) to write all the paths and their lengths to it.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$WriteResultsToFile</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$fileStream</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.IO.StreamWriter</span><span class="p">(</span><span class="nv">$ResultsFilePath</span><span class="p">,</span><span class="w"> </span><span class="bp">$false</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="nv">$filePathsAndLengths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.Collections.ArrayList</span><span class="p">]::</span><span class="n">new</span><span class="p">()</span><span class="w">

</span><span class="c"># Get all file and directory paths and write them if applicable.</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$DirectoryPathToScan</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="nx">FullName</span><span class="p">,</span><span class="w"> </span><span class="p">@{</span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"FullNameLength"</span><span class="p">;</span><span class="w"> </span><span class="nx">Expression</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">(</span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="o">.</span><span class="nf">Length</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Sort-Object</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="nx">FullNameLength</span><span class="w"> </span><span class="nt">-Descending</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">

    </span><span class="nv">$filePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w">
    </span><span class="nv">$length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullNameLength</span><span class="w">

    </span><span class="c"># If this path is long enough, add it to the results.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$length</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$MinimumPathLengthsToShow</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$lineOutput</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$length</span><span class="s2"> : </span><span class="nv">$filePath</span><span class="s2">"</span><span class="w">

        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$WriteResultsToConsole</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="nv">$lineOutput</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$WriteResultsToFile</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$fileStream</span><span class="o">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="nv">$lineOutput</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="nv">$filePathsAndLengths</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="bp">$_</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$WriteResultsToFile</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$fileStream</span><span class="o">.</span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$WriteResultsToGridView</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$filePathsAndLengths</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-GridView</span><span class="w"> </span><span class="nt">-Title</span><span class="w"> </span><span class="s2">"Paths under '</span><span class="nv">$DirectoryPathToScan</span><span class="s2">' longer than '</span><span class="nv">$MinimumPathLengthsToShow</span><span class="s2">'."</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="directory" /><category term="file" /><category term="Length" /><category term="Path" /><category term="PowerShell" /><category term="Subdirectory" /><summary type="html"><![CDATA[A while ago I created a Path Length Checker tool in C# that has a “nice” GUI, and put it up on CodePlex. One of the users reported that he was trying to use it to scan his entire C: drive, but that it was crashing. Turns out that the System.IO.Directory.GetFileSystemEntries() call was throwing a permissions exception when trying to access the “C:\Documents and Settings” directory. Even when running the app as admin it throws this exception. In the meantime while I am working on implementing a workaround for the app, I wrote up a quick PowerShell script that the user could use to get all of the path lengths. That is what I present to you here.]]></summary></entry><entry><title type="html">PowerShell Functions To Convert, Remove, and Delete IIS Web Applications</title><link href="https://blog.danskingdom.com/powershell-functions-to-convert-remove-and-delete-iis-web-applications/" rel="alternate" type="text/html" title="PowerShell Functions To Convert, Remove, and Delete IIS Web Applications" /><published>2013-10-23T20:56:22+00:00</published><updated>2013-10-23T20:56:22+00:00</updated><id>https://blog.danskingdom.com/powershell-functions-to-convert-remove-and-delete-iis-web-applications</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-functions-to-convert-remove-and-delete-iis-web-applications/"><![CDATA[<p>I recently refactored some of our PowerShell scripts that we use to publish and remove IIS 7 web applications, creating some general functions that can be used anywhere. In this post I show these functions along with how I structure our scripts to make creating, removing, and deleting web applications for our various products fully automated and tidy. Note that these scripts require at least PowerShell v3.0 and use the <a href="http://technet.microsoft.com/en-us/library/ee790599.aspx">IIS Admin Cmdlets</a> that I believe require IIS v7.0; the IIS Admin Cmdlet calls can easily be replaced though by calls to appcmd.exe, msdeploy, or any other tool for working with IIS that you want.</p>

<p><a href="/assets/Posts/2013/10/ApplicationServiceScripts2.zip">Download this article’s code here</a>.</p>

<p>I’ll blast you with the first file’s code and explain it below (ApplicationServiceUtilities.ps1).</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Turn on Strict Mode to help catch syntax-related errors.</span><span class="w">
</span><span class="c">#   This must come after a script's/function's param section.</span><span class="w">
</span><span class="c">#   Forces a function to be the first non-comment code to appear in a PowerShell Module.</span><span class="w">
</span><span class="n">Set-StrictMode</span><span class="w"> </span><span class="nt">-Version</span><span class="w"> </span><span class="nx">Latest</span><span class="w">

</span><span class="c"># Define the code block that will add the ApplicationServiceInformation class to the PowerShell session.</span><span class="w">
</span><span class="c"># NOTE: If this class is modified you will need to restart your PowerShell session to see the changes.</span><span class="w">
</span><span class="nv">$AddApplicationServiceInformationTypeScriptBlock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="c"># Wrap in a try-catch in case we try to add this type twice.</span><span class="w">
    </span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="c"># Create a class to hold an IIS Application Service's Information.</span><span class="w">
    </span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-TypeDefinition</span><span class="w"> </span><span class="s2">"
        using System;

        public class ApplicationServiceInformation
        {
            // The name of the Website in IIS.
            public string Website { get; set;}

            // The path to the Application, relative to the Website root.
            public string ApplicationPath { get; set; }

            // The Application Pool that the application is running in.
            public string ApplicationPool { get; set; }

            // Whether this application should be published or not.
            public bool ConvertToApplication { get; set; }

            // Implicit Constructor.
            public ApplicationServiceInformation() { this.ConvertToApplication = true; }

            // Explicit constructor.
            public ApplicationServiceInformation(string website, string applicationPath, string applicationPool, bool convertToApplication = true)
            {
                this.Website = website;
                this.ApplicationPath = applicationPath;
                this.ApplicationPool = applicationPool;
                this.ConvertToApplication = convertToApplication;
            }
        }
    "</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="c"># Add the ApplicationServiceInformation class to this PowerShell session.</span><span class="w">
</span><span class="o">&amp;</span><span class="w"> </span><span class="nv">$AddApplicationServiceInformationTypeScriptBlock</span><span class="w">

</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Converts the given files to application services on the given Server.

    </span><span class="cs">.PARAMETER</span><span class="cm"> Server
    The Server Host Name to connect to and convert the applications on.

    </span><span class="cs">.PARAMETER</span><span class="cm"> ApplicationServicesInfo
    The [ApplicationServiceInformation[]] containing the files to convert to application services.
#&gt;</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">ConvertTo-ApplicationServices</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Server</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$ApplicationServicesInfo</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="nv">$block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="p">([</span><span class="n">PSCustomObject</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
        </span><span class="nv">$VerbosePreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">Using</span><span class="p">:</span><span class="nv">VerbosePreference</span><span class="w">
        </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Converting To Application Services..."</span><span class="w">

        </span><span class="c"># Import the WebAdministration module to make sure we have access to the required cmdlets and the IIS: drive.</span><span class="w">
        </span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">WebAdministration</span><span class="w"> </span><span class="nx">4</span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">    </span><span class="c"># Don't write the verbose output.</span><span class="w">

        </span><span class="c"># Create all of the Web Applications, making sure to first try and remove them in case they already exist (in order to avoid a PS error).</span><span class="w">
        </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$appInfo</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="p">[</span><span class="n">PSCustomObject</span><span class="p">[]]</span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">Website</span><span class="w">
            </span><span class="nv">$applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">ApplicationPath</span><span class="w">
            </span><span class="nv">$applicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">ApplicationPool</span><span class="w">
            </span><span class="nv">$fullPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$website</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">

            </span><span class="c"># If this application should not be converted, continue onto the next one in the list.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">ConvertToApplication</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Skipping publish of '</span><span class="nv">$fullPath</span><span class="s2">'"</span><span class="p">;</span><span class="w"> </span><span class="kr">continue</span><span class="w"> </span><span class="p">}</span><span class="w">

            </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Checking if we need to remove '</span><span class="nv">$fullPath</span><span class="s2">' before converting it..."</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Get-WebApplication</span><span class="w"> </span><span class="nt">-Site</span><span class="w"> </span><span class="s2">"</span><span class="nv">$website</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"</span><span class="nv">$applicationPath</span><span class="s2">"</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Removing '</span><span class="nv">$fullPath</span><span class="s2">'..."</span><span class="w">
                </span><span class="n">Remove-WebApplication</span><span class="w"> </span><span class="nt">-Site</span><span class="w"> </span><span class="s2">"</span><span class="nv">$website</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"</span><span class="nv">$applicationPath</span><span class="s2">"</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Converting '</span><span class="nv">$fullPath</span><span class="s2">' to an application with Application Pool '</span><span class="nv">$applicationPool</span><span class="s2">'..."</span><span class="w">
            </span><span class="n">ConvertTo-WebApplication</span><span class="w"> </span><span class="s2">"IIS:\Sites\</span><span class="nv">$fullPath</span><span class="s2">"</span><span class="w"> </span><span class="nt">-ApplicationPool</span><span class="w"> </span><span class="s2">"</span><span class="nv">$applicationPool</span><span class="s2">"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Connect to the host Server and run the commands directly o that computer.</span><span class="w">
    </span><span class="c"># Before we run our script block we first have to add the ApplicationServiceInformation class type into the PowerShell session.</span><span class="w">
    </span><span class="nv">$session</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-PSSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$Server</span><span class="w">
    </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$AddApplicationServiceInformationTypeScriptBlock</span><span class="w">
    </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$block</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="p">(,</span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
    </span><span class="n">Remove-PSSession</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Removes the given application services from the given Server.

    </span><span class="cs">.PARAMETER</span><span class="cm"> Server
    The Server Host Name to connect to and remove the applications from.

    </span><span class="cs">.PARAMETER</span><span class="cm"> ApplicationServicesInfo
    The [ApplicationServiceInformation[]] containing the applications to remove.
#&gt;</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Remove-ApplicationServices</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Server</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$ApplicationServicesInfo</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="nv">$block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="p">([</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
        </span><span class="nv">$VerbosePreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">Using</span><span class="p">:</span><span class="nv">VerbosePreference</span><span class="w">
        </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Removing Application Services..."</span><span class="w">

        </span><span class="c"># Import the WebAdministration module to make sure we have access to the required cmdlets and the IIS: drive.</span><span class="w">
        </span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">WebAdministration</span><span class="w"> </span><span class="nx">4</span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">    </span><span class="c"># Don't write the verbose output.</span><span class="w">

        </span><span class="c"># Remove all of the Web Applications, making sure they exist first (in order to avoid a PS error).</span><span class="w">
        </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$appInfo</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">Website</span><span class="w">
            </span><span class="nv">$applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">ApplicationPath</span><span class="w">
            </span><span class="nv">$fullPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$website</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">

            </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Checking if we need to remove '</span><span class="nv">$fullPath</span><span class="s2">'..."</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Get-WebApplication</span><span class="w"> </span><span class="nt">-Site</span><span class="w"> </span><span class="s2">"</span><span class="nv">$website</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"</span><span class="nv">$applicationPath</span><span class="s2">"</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Removing '</span><span class="nv">$fullPath</span><span class="s2">'..."</span><span class="w">
                </span><span class="n">Remove-WebApplication</span><span class="w"> </span><span class="nt">-Site</span><span class="w"> </span><span class="s2">"</span><span class="nv">$website</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"</span><span class="nv">$applicationPath</span><span class="s2">"</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Connect to the host Server and run the commands directly on that computer.</span><span class="w">
    </span><span class="c"># Before we run our script block we first have to add the ApplicationServiceInformation class type into the PowerShell session.</span><span class="w">
    </span><span class="nv">$session</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-PSSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$Server</span><span class="w">
    </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$AddApplicationServiceInformationTypeScriptBlock</span><span class="w">
    </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$block</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="p">(,</span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
    </span><span class="n">Remove-PSSession</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Removes the given application services from the given Server and deletes all associated files.

    </span><span class="cs">.PARAMETER</span><span class="cm"> Server
    The Server Host Name to connect to and delete the applications from.

    </span><span class="cs">.PARAMETER</span><span class="cm"> ApplicationServicesInfo
    The [ApplicationServiceInformation[]] containing the applications to delete.

    </span><span class="cs">.PARAMETER</span><span class="cm"> OnlyDeleteIfNotConvertedToApplication
    If this switch is supplied and the application services are still running (i.e. have not been removed yet), the services will not be removed and the files will not be deleted.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DeleteEmptyParentDirectories
    If this switch is supplied, after the application services folder has been removed, it will recursively check parent folders and remove them if they are empty, until the Website root is reached.
#&gt;</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Delete-ApplicationServices</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">()]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Server</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$ApplicationServicesInfo</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$OnlyDeleteIfNotConvertedToApplication</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DeleteEmptyParentDirectories</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="nv">$block</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="p">([</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
        </span><span class="nv">$VerbosePreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">Using</span><span class="p">:</span><span class="nv">VerbosePreference</span><span class="w">
        </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Deleting Application Services..."</span><span class="w">

        </span><span class="c"># Import the WebAdministration module to make sure we have access to the required cmdlets and the IIS: drive.</span><span class="w">
        </span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">WebAdministration</span><span class="w"> </span><span class="nx">4</span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">    </span><span class="c"># Don't write the verbose output.</span><span class="w">

        </span><span class="c"># Remove all of the Web Applications and delete their files from disk.</span><span class="w">
        </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$appInfo</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">Website</span><span class="w">
            </span><span class="nv">$applicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="o">.</span><span class="nf">ApplicationPath</span><span class="w">
            </span><span class="nv">$fullPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$website</span><span class="w"> </span><span class="nv">$applicationPath</span><span class="w">
            </span><span class="nv">$iisSitesDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"IIS:\Sites\"</span><span class="w">

            </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Checking if we need to remove '</span><span class="nv">$fullPath</span><span class="s2">'..."</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Get-WebApplication</span><span class="w"> </span><span class="nt">-Site</span><span class="w"> </span><span class="s2">"</span><span class="nv">$website</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"</span><span class="nv">$applicationPath</span><span class="s2">"</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># If we should only delete the files they're not currently running as a Web Application, continue on to the next one in the list.</span><span class="w">
                </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$</span><span class="nn">Using</span><span class="p">:</span><span class="nv">OnlyDeleteIfNotConvertedToApplication</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"'</span><span class="nv">$fullPath</span><span class="s2">' is still running as a Web Application, so its files will not be deleted."</span><span class="p">;</span><span class="w"> </span><span class="kr">continue</span><span class="w"> </span><span class="p">}</span><span class="w">

                </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Removing '</span><span class="nv">$fullPath</span><span class="s2">'..."</span><span class="w">
                </span><span class="n">Remove-WebApplication</span><span class="w"> </span><span class="nt">-Site</span><span class="w"> </span><span class="s2">"</span><span class="nv">$website</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"</span><span class="nv">$applicationPath</span><span class="s2">"</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Deleting the directory '</span><span class="nv">$fullPath</span><span class="s2">'..."</span><span class="w">
            </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$iisSitesDirectory$fullPath</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w">

            </span><span class="c"># If we should delete empty parent directories of this application.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$</span><span class="nn">Using</span><span class="p">:</span><span class="nv">DeleteEmptyParentDirectories</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Deleting empty parent directories..."</span><span class="w">
                </span><span class="nv">$parent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$fullPath</span><span class="w"> </span><span class="nt">-Parent</span><span class="w">

                </span><span class="c"># Only delete the parent directory if it is not the Website directory, and it is empty.</span><span class="w">
                </span><span class="kr">while</span><span class="w"> </span><span class="p">((</span><span class="nv">$parent</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$website</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$iisSitesDirectory$parent</span><span class="s2">"</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">((</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$iisSitesDirectory$parent</span><span class="s2">"</span><span class="p">)</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$parent</span><span class="w">
                    </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Deleting empty parent directory '</span><span class="nv">$path</span><span class="s2">'..."</span><span class="w">
                    </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"</span><span class="nv">$iisSitesDirectory$path</span><span class="s2">"</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
                    </span><span class="nv">$parent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$path</span><span class="w"> </span><span class="nt">-Parent</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Connect to the host Server and run the commands directly on that computer.</span><span class="w">
    </span><span class="c"># Before we run our script block we first have to add the ApplicationServiceInformation class type into the PowerShell session.</span><span class="w">
    </span><span class="nv">$session</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-PSSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$Server</span><span class="w">
    </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$AddApplicationServiceInformationTypeScriptBlock</span><span class="w">
    </span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="nv">$block</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="p">(,</span><span class="nv">$ApplicationServicesInfo</span><span class="p">)</span><span class="w">
    </span><span class="n">Remove-PSSession</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This first file contains all of the meat. At the top it declares (in C#) the <strong>ApplicationServiceInformation</strong> class that is used to hold the information about a web application; mainly the Website that the application should go in, the ApplicationPath (where within the website the application should be created), and the Application Pool that the application should run under. Notice that the $AddApplicationServiceInformationTypeScriptBlock script block is executed right below where it is declared, in order to actually import the ApplicationServiceInformation class type into the current PowerShell session.</p>

<p>There is one extra property on this class that I found I needed, but you may be able to ignore; that is the ConvertToApplication boolean. This is inspected by our ConvertTo-ApplicationServices function to tell it whether the application should actually be published or not. I required this field because we have some web services that should only be “converted to applications” in specific environments (or only on a developers local machine), but whose files we still want to delete when using the Delete-ApplicationServices function. While I could just create 2 separate lists of ApplicationServiceInformation objects depending on which function I was calling (see below), I decided to instead just include this one extra property.</p>

<p>Below the class declaration are our functions to perform the actual work:</p>

<ul>
  <li>ConvertTo-ApplicationServices: Converts the files to an application using the ConvertTo-WebApplication cmdlet.</li>
  <li>Remove-ApplicationServices: Converts the application back to regular files using the Remove-WebApplication cmdlet.</li>
  <li>Delete-ApplicationServices: First removes any applications, and then deletes the files from disk.</li>
</ul>

<p>The Delete-ApplicationServices function includes an couple additional switches. The <strong>$OnlyDeleteIfNotConvertedToApplication</strong> switch can be used as a bit of a safety net to ensure that you only delete files for application services that are not currently running as a web application (i.e. the web application has already been removed). If this switch is omitted, the web application will be removed and the files deleted. The <strong>$DeleteEmptyParentDirectories</strong> switch that may be used to remove parent directories once the application files have been deleted. This is useful for us because we version our services, so they are all placed in a directory corresponding to a version number. e.g. \Website[VersionNumber]\App1 and \Website[VersionNumber]\App2. This switch allows the [VersionNumber] directory to be deleted automatically once the App1 and App2 directories have been deleted.</p>

<p>Note that I don’t have a function to copy files to the server (i.e. publish them); I assume that the files have already been copied to the server, as we currently have this as a separate step in our deployment process.</p>

<p>My 2nd file (ApplicationServiceLibrary.ps1) is optional and is really just a collection of functions used to return the ApplicationServiceInformation instances that I require as an array, depending on which projects I want to convert/remove/delete.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Get the directory that this script is in.</span><span class="w">
</span><span class="nv">$THIS_SCRIPTS_DIRECTORY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nv">$</span><span class="nn">script</span><span class="p">:</span><span class="nv">MyInvocation</span><span class="o">.</span><span class="nf">MyCommand</span><span class="o">.</span><span class="nf">Path</span><span class="w">

</span><span class="c"># Include the required ApplicationServiceInformation type.</span><span class="w">
</span><span class="o">.</span><span class="w"> </span><span class="p">(</span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$THIS_SCRIPTS_DIRECTORY</span><span class="w"> </span><span class="nx">ApplicationServiceUtilities.ps1</span><span class="p">)</span><span class="w">

</span><span class="c">#=================================</span><span class="w">
</span><span class="c"># Replace all of the functions below with your own.</span><span class="w">
</span><span class="c"># These are provided as examples.</span><span class="w">
</span><span class="c">#=================================</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-AllApplicationServiceInformation</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Release</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$appServiceInfo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@()</span><span class="w">

    </span><span class="nv">$appServiceInfo</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">Get-RqApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Release</span><span class="w"> </span><span class="nv">$Release</span><span class="w">
    </span><span class="nv">$appServiceInfo</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">Get-PublicApiApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Release</span><span class="w"> </span><span class="nv">$Release</span><span class="w">
    </span><span class="nv">$appServiceInfo</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">Get-IntraApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Release</span><span class="w"> </span><span class="nv">$Release</span><span class="w">

    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$appServiceInfo</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-RqApplicationServiceInformation</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Release</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="err">(New-Object</span><span class="w"> </span><span class="err">ApplicationServiceInformation</span><span class="w"> </span><span class="err">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Application Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">/Core.Reporting.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"RQ Services .NET4"</span><span class="p">}),</span><span class="w">
        </span><span class="p">(</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Application Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">/Core.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"RQ Core Services .NET4"</span><span class="p">}),</span><span class="w">
        </span><span class="p">(</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Application Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">/DeskIntegration.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"RQ Services .NET4"</span><span class="p">}),</span><span class="w">
        </span><span class="p">(</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Application Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">/Retail.Integration.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"RQ Services .NET4"</span><span class="p">}),</span><span class="w">

        </span><span class="c"># Simulator Services that are only for Dev; we don't want to convert them to an application, but do want to remove their files that got copied to the web server.</span><span class="w">
        </span><span class="p">(</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Application Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">/Simulator.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Simulator Services .NET4"</span><span class="p">;</span><span class="w"> </span><span class="nx">ConvertToApplication</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">}))</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-PublicApiApplicationServiceInformation</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Release</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="err">(New-Object</span><span class="w"> </span><span class="err">ApplicationServiceInformation</span><span class="w"> </span><span class="err">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"API Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">/PublicAPI.Host"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"API Services .NET4"</span><span class="p">}),</span><span class="w">
        </span><span class="p">(</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"API Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">/PublicAPI.Documentation"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"API Services .NET4"</span><span class="p">}))</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-IntraApplicationServiceInformation</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Release</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="err">(New-Object</span><span class="w"> </span><span class="err">ApplicationServiceInformation</span><span class="w"> </span><span class="err">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Intra Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Release</span><span class="s2">"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Intra Services .NET4"</span><span class="p">}))</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can see the first thing it does is dot source the ApplicationServiceUtilities.ps1 file (<em>I assume all these scripts are in the same directory</em>). This is done in order to include the ApplicationServiceInformation type into the PowerShell session. Next I just have functions that return the various application service information that our various projects specify. I break them apart by project so that I’m able to easily publish one project separately from another, but also have a Get-All function that returns back all of the service information for when we deploy all services together. We deploy many of our projects in lock-step, so having a Get-All function makes sense for us, but it may not for you. We have many more projects and services than I show here; I just show these as an example of how you can set yours up if you choose.</p>

<p>One other thing you may notice is that my Get-*ApplicationServiceInformation functions take a $Release parameter that is used in the ApplicationPath; this is because our services are versioned. Yours may not be though, in which case you can omit that parameter for your Get functions (or add any additional parameters that you do need).</p>

<p>Lastly, to make things nice and easy, I create ConvertTo, Remove, and Delete scripts for each of our projects, as well as a scripts to do all of the projects at once. Here’s an example of what one of these scripts would look like:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">param</span><span class="w">
</span><span class="p">(</span><span class="w">
    </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">,</span><span class="n">HelpMessage</span><span class="o">=</span><span class="s2">"The 3 hex-value version number of the release (x.x.x)."</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">ValidatePattern</span><span class="p">(</span><span class="s2">"^\d{1,5}\.\d{1,5}\.\d{1,5}$"</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Release</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c"># Get the directory that this script is in.</span><span class="w">
</span><span class="nv">$THIS_SCRIPTS_DIRECTORY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nv">$</span><span class="nn">script</span><span class="p">:</span><span class="nv">MyInvocation</span><span class="o">.</span><span class="nf">MyCommand</span><span class="o">.</span><span class="nf">Path</span><span class="w">

</span><span class="c"># Include the functions used to perform the actual operations.</span><span class="w">
</span><span class="o">.</span><span class="w"> </span><span class="p">(</span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$THIS_SCRIPTS_DIRECTORY</span><span class="w"> </span><span class="nx">ApplicationServiceLibrary.ps1</span><span class="p">)</span><span class="w">

</span><span class="n">ConvertTo-ApplicationServices</span><span class="w"> </span><span class="nt">-Server</span><span class="w"> </span><span class="s2">"Our.WebServer.local"</span><span class="w"> </span><span class="nt">-ApplicationServicesInfo</span><span class="w"> </span><span class="p">(</span><span class="n">Get-RqApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Release</span><span class="w"> </span><span class="nv">$Release</span><span class="p">)</span><span class="w"> </span><span class="nt">-Verbose</span><span class="w">
</span></code></pre></div></div>

<p>The first thing it does is prompt for the $Release version number; again, if you don’t version your services then you can omit that.</p>

<p>The next thing it does is dot-source the ApplicationServicesLibrary.ps1 script to make all of the Get-*ApplicationServiceInformation functions that we defined in the previous file available. I prefer to use the ApplicationServicesLibrary.ps1 file to place all of our services in a common place, and to avoid copy/pasting the ApplicationServiceInformation for each project into each Convert/Remove/Delete script; but that’s my personal choice and if you prefer to copy-paste the code into a few different files instead of having a central library file, go hard. If you omit the Library script though, then you will instead need to dot-source the ApplicationServiceUtilities.ps1 file here, since our Library script currently dot-sources it in for us.</p>

<p>The final line is the one that actually calls our utility function to perform the operation. It provides the web server hostname to connect to, and calls the library’s Get-*ApplicationServiceInformation to retrieve the information for the web applications that should be created. Notice too that it also provides the –Verbose switch. Some of the IIS operations can take quite a while to run and don’t generate any output, so I like to see the verbose output so I can gauge the progress of the script, but feel free to omit it.</p>

<p>So this sample script creates all of the web applications for our Rq product and can be ran very easily. To make the corresponding Remove and Delete scripts, I would just copy this file and replace “ConvertTo-“ with “Remove-“ and “Delete-“ respectively. This allows you to have separate scripts for creating and removing each of your products that can easily be ran automatically or manually, fully automating the process of creating and removing your web applications in IIS.</p>

<p>If I need to remove the services for a bunch of versions, here is an example of how I can just create a quick script that calls my Remove Services script for each version that needs to be removed:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Get the directory that this script is in.</span><span class="w">
</span><span class="bp">$this</span><span class="n">ScriptsDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nv">$</span><span class="nn">script</span><span class="p">:</span><span class="nv">MyInvocation</span><span class="o">.</span><span class="nf">MyCommand</span><span class="o">.</span><span class="nf">Path</span><span class="w">

</span><span class="c"># Remove Rq application services for versions 4.11.33 to 4.11.43.</span><span class="w">
</span><span class="nv">$majorMinorVersion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"4.11"</span><span class="w">
</span><span class="mi">33</span><span class="o">..</span><span class="mi">43</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kr">foreach</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$Release</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$majorMinorVersion</span><span class="s2">.</span><span class="bp">$_</span><span class="s2">"</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Removing Rq '</span><span class="nv">$Release</span><span class="s2">' services..."</span><span class="w">
    </span><span class="o">&amp;</span><span class="w"> </span><span class="s2">"</span><span class="bp">$this</span><span class="s2">ScriptsDirectory\Remove-RqServices.ps1"</span><span class="w"> </span><span class="nv">$Release</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>If you have any questions or suggestions feel free to leave a comment. I hope you find this useful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="IIS" /><category term="PowerShell" /><category term="Application" /><category term="Convert" /><category term="Create" /><category term="Delete" /><category term="IIS" /><category term="PowerShell" /><category term="Remove" /><category term="Service" /><category term="Web" /><summary type="html"><![CDATA[I recently refactored some of our PowerShell scripts that we use to publish and remove IIS 7 web applications, creating some general functions that can be used anywhere. In this post I show these functions along with how I structure our scripts to make creating, removing, and deleting web applications for our various products fully automated and tidy. Note that these scripts require at least PowerShell v3.0 and use the IIS Admin Cmdlets that I believe require IIS v7.0; the IIS Admin Cmdlet calls can easily be replaced though by calls to appcmd.exe, msdeploy, or any other tool for working with IIS that you want.]]></summary></entry><entry><title type="html">PowerShell 2.0 vs. 3.0 Syntax Differences And More</title><link href="https://blog.danskingdom.com/powershell-2-0-vs-3-0-syntax-differences-and-more/" rel="alternate" type="text/html" title="PowerShell 2.0 vs. 3.0 Syntax Differences And More" /><published>2013-10-22T17:23:21+00:00</published><updated>2013-10-22T17:23:21+00:00</updated><id>https://blog.danskingdom.com/powershell-2-0-vs-3-0-syntax-differences-and-more</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-2-0-vs-3-0-syntax-differences-and-more/"><![CDATA[<p>I’m fortunate enough to work for <a href="http://www.iqmetrix.com/">a great company</a> that tries to stay ahead of the curve and use newer technologies. This means that when I’m writing my PowerShell (PS) scripts I typically don’t have to worry about only using PS v2.0 compatible syntax and cmdlets, as all of our PCs have v3.0 (soon to have v4.0). This is great, until I release these scripts (or snippets from the scripts) for the general public to use; I have to keep in mind that many other people are still stuck running older versions of Windows, or not allowed to upgrade PowerShell. So to help myself release PS v2.0 compatible scripts to the general public, I’m going to use this as a living document of the differences between PowerShell 2.0 and 3.0 that I encounter (so it will continue to grow over time; read as, bookmark it). Of course there are other sites that have some of this info, but I’m going to try and compile a list of the ones that are relevant to me, in a nice simple format.</p>

<p>Before we get to the differences, here are some things you may want to know relating to PowerShell versions.</p>

<h2 id="how-to-check-which-version-of-powershell-you-are-running">How to check which version of PowerShell you are running</h2>

<p>All PS versions:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">$PSVersionTable</span><span class="o">.</span><span class="nf">PSVersion</span><span class="w">
</span></code></pre></div></div>

<h2 id="how-to-runtest-your-script-against-an-older-version-of-powershell-source">How to run/test your script against an older version of PowerShell (<a href="http://technet.microsoft.com/en-us/library/hh847899.aspx">source</a>)</h2>

<p>All PS versions: use <strong>PowerShell.exe –Version [version]</strong> to start a new PowerShell session, where [version] is the PowerShell version that you want the session to use, then run your script in this new session. Shorthand is <strong>PowerShell –v [version]</strong></p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PowerShell.exe</span><span class="w"> </span><span class="nt">-Version</span><span class="w"> </span><span class="nx">2.0</span><span class="w">
</span></code></pre></div></div>

<p>Note: <a href="http://stackoverflow.com/questions/18919862/start-powershell-ise-with-the-2-0-runtime">You can’t run PowerShell ISE in an older version of PowerShell</a>; only the Windows PowerShell console.</p>

<h2 id="powershell-v2-and-v3-differences">PowerShell v2 and v3 Differences</h2>

<h3 id="where-object-no-longer-requires-braces-source">Where-Object no longer requires braces (<a href="http://blogs.technet.com/b/heyscriptingguy/archive/2012/08/20/my-five-favorite-powershell-3-0-tips-and-tricks.aspx">source</a>)</h3>

<p>PS v2.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Service</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Status</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'running'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>PS v3.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-Service</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where</span><span class="w"> </span><span class="nx">Status</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'running'</span><span class="w">
</span></code></pre></div></div>

<p>PS V2.0 Error Message:</p>

<blockquote>
  <p>Where : Cannot bind parameter ‘FilterScript’. Cannot convert the “[PropertyName]” value of the type “[Type]” to type “System.Management.Automation.ScriptBlock”.</p>
</blockquote>

<h3 id="using-local-variables-in-remote-sessions-source">Using local variables in remote sessions (<a href="http://blogs.technet.com/b/heyscriptingguy/archive/2012/08/20/my-five-favorite-powershell-3-0-tips-and-tricks.aspx">source</a>)</h3>

<p>PS v2.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$class</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"win32_bios"</span><span class="w">
</span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-cn</span><span class="w"> </span><span class="nx">dc3</span><span class="w"> </span><span class="p">{</span><span class="kr">param</span><span class="p">(</span><span class="nv">$class</span><span class="p">)</span><span class="w"> </span><span class="n">gwmi</span><span class="w"> </span><span class="nt">-class</span><span class="w"> </span><span class="nv">$class</span><span class="p">}</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="nv">$class</span><span class="w">
</span></code></pre></div></div>

<p>PS v3.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$class</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"win32_bios"</span><span class="w">
</span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-cn</span><span class="w"> </span><span class="nx">dc3</span><span class="w"> </span><span class="p">{</span><span class="n">gwmi</span><span class="w"> </span><span class="nt">-class</span><span class="w"> </span><span class="nv">$</span><span class="nn">Using</span><span class="p">:</span><span class="nv">class</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="variable-validation-attributes-source">Variable validation attributes (<a href="http://blogs.technet.com/b/heyscriptingguy/archive/2012/08/20/my-five-favorite-powershell-3-0-tips-and-tricks.aspx">source</a>)</h3>

<p>PS v2.0: Validation only available on cmdlet/function/script parameters.</p>

<p>PS v3.0: Validation available on cmdlet/function/script parameters, and on variables.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">ValidateRange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">5</span><span class="p">)][</span><span class="n">int</span><span class="p">]</span><span class="nv">$someLocalVariable</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></code></pre></div></div>

<h3 id="stream-redirection-source">Stream redirection (<a href="http://technet.microsoft.com/en-us/library/hh847746.aspx">source</a>)</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The Windows PowerShell redirection operators use the following characters to represent each output type:
  *   All output
  1   Success output
  2   Errors
  3   Warning messages
  4   Verbose output
  5   Debug messages

NOTE: The All (*), Warning (3), Verbose (4) and Debug (5) redirection operators were introduced
  in Windows PowerShell 3.0. They do not work in earlier versions of Windows PowerShell.
</code></pre></div></div>

<p>PS v2.0: Could only redirect Success and Error output.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Sends errors (2) and success output (1) to the success output stream.</span><span class="w">
</span><span class="n">Get-Process</span><span class="w"> </span><span class="nx">none</span><span class="p">,</span><span class="w"> </span><span class="nx">Powershell</span><span class="w"> </span><span class="nx">2</span><span class="err">&gt;</span><span class="o">&amp;</span><span class="nx">1</span><span class="w">
</span></code></pre></div></div>

<p>PS v3.0: Can also redirect Warning, Verbose, Debug, and All output.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Function to generate each kind of output.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Test-Output</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Get-Process</span><span class="w"> </span><span class="nx">PowerShell</span><span class="p">,</span><span class="w"> </span><span class="nx">none</span><span class="p">;</span><span class="w"> </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"Test!"</span><span class="p">;</span><span class="w"> </span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"Test Verbose"</span><span class="p">;</span><span class="w"> </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Test Debug"</span><span class="p">}</span><span class="w">

</span><span class="c"># Write every output stream to a text file.</span><span class="w">
</span><span class="n">Test-Output</span><span class="w"> </span><span class="o">*</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">Test-Output.txt</span><span class="w">
</span></code></pre></div></div>

<h3 id="explicitly-set-parameter-set-variable-values-when-not-defined-source">Explicitly set parameter set variable values when not defined (<a href="https://blog.danskingdom.com/always-explicitly-set-your-parameter-set-variables-for-powershell-v2-0-compatibility/">source</a>)</h3>

<p>PS v2.0 will throw an error if you try and access a parameter set parameter that has not been defined. The solution is to give it a default value when it is not defined. Specify the Private scope in case a variable with the same name exists in the global scope or an inherited scope:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Default the ParameterSet variables that may not have been set depending on which parameter set is being used.</span><span class="w">
</span><span class="c"># This is required for PowerShell v2.0 compatibility.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nx">Variable:Private:SomeStringParameter</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$SomeStringParameter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nx">Variable:Private:SomeIntegerParameter</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$SomeIntegerParameter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nx">Variable:Private:SomeSwitchParameter</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$SomeSwitchParameter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>PS v2.0 Error Message:</p>

<blockquote>
  <p>The variable ‘$[VariableName]’ cannot be retrieved because it has not been set.</p>
</blockquote>

<h3 id="parameter-attributes-require-the-equals-sign">Parameter attributes require the equals sign</h3>

<p>PS v2.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)]</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$SomeParameter</span><span class="w">
</span></code></pre></div></div>

<p>PS v3.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">Mandatory</span><span class="p">)]</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$SomeParameter</span><span class="w">
</span></code></pre></div></div>

<p>PS v2.0 Error Message:</p>

<blockquote>
  <p>The “=” operator is missing after a named argument.</p>
</blockquote>

<h3 id="cannot-use-stringisnullorwhitespace-or-any-other-post-net-35-functionality">Cannot use String.IsNullOrWhitespace (or any other post .Net 3.5 functionality)</h3>

<p>PS v2.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$SomeString</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>PS v3.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrWhiteSpace</span><span class="p">(</span><span class="nv">$SomeString</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>PS v2.0 Error Message:</p>

<blockquote>
  <p>IsNullOrWhitespace : Method invocation failed because [System.String] doesn’t contain a method named ‘IsNullOrWhiteSpace’.</p>
</blockquote>

<p>PS v2.0 compatible version of IsNullOrWhitespace function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># PowerShell v2.0 compatible version of [string]::IsNullOrWhitespace.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">StringIsNullOrWhitespace</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$string</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$string</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$string</span><span class="o">.</span><span class="nf">Trim</span><span class="p">()</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$string</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="get-childitem-cmdlets-directory-and-file-switches-were-introduced-in-ps-v30">Get-ChildItem cmdlet’s –Directory and –File switches were introduced in PS v3.0</h3>

<p>PS v2.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$somePath</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="p">}</span><span class="w">   </span><span class="c"># Get directories only.</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$somePath</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">!</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="p">}</span><span class="w">  </span><span class="c"># Get files only.</span><span class="w">
</span></code></pre></div></div>

<p>PS v3.0:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$somePath</span><span class="w"> </span><span class="nt">-Directory</span><span class="w">
</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$somePath</span><span class="w"> </span><span class="nt">-File</span><span class="w">
</span></code></pre></div></div>

<h3 id="other-links">Other Links</h3>

<ul>
  <li><a href="http://technet.microsoft.com/en-us/library/hh857339.aspx">What’s New in Windows PowerShell</a></li>
</ul>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="2" /><category term="2.0" /><category term="3" /><category term="3.0" /><category term="changes" /><category term="differences" /><category term="New" /><category term="PowerShell" /><category term="syntax" /><category term="Version" /><summary type="html"><![CDATA[I’m fortunate enough to work for a great company that tries to stay ahead of the curve and use newer technologies. This means that when I’m writing my PowerShell (PS) scripts I typically don’t have to worry about only using PS v2.0 compatible syntax and cmdlets, as all of our PCs have v3.0 (soon to have v4.0). This is great, until I release these scripts (or snippets from the scripts) for the general public to use; I have to keep in mind that many other people are still stuck running older versions of Windows, or not allowed to upgrade PowerShell. So to help myself release PS v2.0 compatible scripts to the general public, I’m going to use this as a living document of the differences between PowerShell 2.0 and 3.0 that I encounter (so it will continue to grow over time; read as, bookmark it). Of course there are other sites that have some of this info, but I’m going to try and compile a list of the ones that are relevant to me, in a nice simple format.]]></summary></entry><entry><title type="html">Creating Strongly Typed Objects In PowerShell, Rather Than Using An Array Or PSCustomObject</title><link href="https://blog.danskingdom.com/creating-strongly-typed-objects-in-powershell-rather-than-using-an-array-or-pscustomobject/" rel="alternate" type="text/html" title="Creating Strongly Typed Objects In PowerShell, Rather Than Using An Array Or PSCustomObject" /><published>2013-10-21T20:20:17+00:00</published><updated>2013-10-21T20:20:17+00:00</updated><id>https://blog.danskingdom.com/creating-strongly-typed-objects-in-powershell-rather-than-using-an-array-or-pscustomobject</id><content type="html" xml:base="https://blog.danskingdom.com/creating-strongly-typed-objects-in-powershell-rather-than-using-an-array-or-pscustomobject/"><![CDATA[<p>I recently <a href="http://www.happysysadm.com/2013/10/powershell-hashtables-dictionaries-and.html">read a great article</a> that explained how to create hashtables, dictionaries, and PowerShell objects. I already knew a bit about these, but this article gives a great comparison between them, when to use each of them, and how to create them in the different versions of PowerShell.</p>

<p>Right now I’m working on refactoring some existing code into some general functions for creating, removing, and destroying IIS applications (<a href="https://blog.danskingdom.com/powershell-functions-to-convert-remove-and-delete-iis-web-applications/">read about it here</a>). At first, I thought that this would be a great place to use PSCustomObject, as in order to perform these operations I needed 3 pieces of information about a website; the Website name, the Application Name (essentially the path to the application under the Website root), and the Application Pool that the application should run in.</p>

<h2 id="using-an-array">Using an array</h2>

<p>So initially the code I wrote just used an array to hold the 3 properties of each application service:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Store app service info as an array of arrays.</span><span class="w">
</span><span class="nv">$AppServices</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="err">(</span><span class="s2">"MyWebsite"</span><span class="p">,</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Version</span><span class="s2">/Reporting.Services"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Services .NET4"</span><span class="p">),</span><span class="w">
    </span><span class="p">(</span><span class="s2">"MyWebsite"</span><span class="p">,</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Version</span><span class="s2">/Core.Services"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Services .NET4"</span><span class="p">),</span><span class="w">
    </span><span class="o">...</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c"># Remove all of the Web Applications.</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$appInfo</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$AppServices</span><span class="w"> </span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w">
    </span><span class="nv">$appName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w">
    </span><span class="nv">$appPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$appInfo</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w">
    </span><span class="o">...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>There is nothing “wrong” with using an array to store the properties; it works. However, now that I am refactoring the functions to make them general purpose to be used by other people/scripts, this does have one very undesirable limitation; The properties must always be stored in the correct order in the array (i.e. Website in position 0, App Name in 1, and App Pool in 2). Since the list of app services will be passed into my functions, this would require the calling script to know to put the properties in this order. Boo.</p>

<p>Another option that I didn’t consider when I originally wrote the script was to use an associative array, but it has the same drawbacks as using a PSCustomObject discussed below.</p>

<h2 id="using-pscustomobject">Using PSCustomObject</h2>

<p>So I thought let’s use a PSCustomObject instead, as that way the client does not have to worry about the order of the information; as long as their PSCustomObject has Website, ApplicationPath, and ApplicationPool properties then we’ll be able to process it. So I had this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">PSCustomObject</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$applicationServicesInfo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="p">[</span><span class="n">PSCustomObject</span><span class="p">]@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"MyWebsite"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Version</span><span class="s2">/Reporting.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Services .NET4"</span><span class="p">},</span><span class="w">
    </span><span class="p">[</span><span class="n">PSCustomObject</span><span class="p">]@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"MyWebsite"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Version</span><span class="s2">/Core.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Services .NET4},
    ...
)

function Remove-ApplicationServices
{
    param([PSCustomObject[]] </span><span class="nv">$ApplicationServicesInfo</span><span class="s2">)

    # Remove all of the Web Applications.
    foreach (</span><span class="nv">$appInfo</span><span class="s2"> in [PSCustomObject[]]</span><span class="nv">$ApplicationServicesInfo</span><span class="s2">)
    {
        </span><span class="nv">$website</span><span class="s2"> = </span><span class="nv">$appInfo</span><span class="s2">.Website
        </span><span class="nv">$appPath</span><span class="s2"> = </span><span class="nv">$appInfo</span><span class="s2">.ApplicationPath
        </span><span class="nv">$appPool</span><span class="s2"> = </span><span class="nv">$appInfo</span><span class="s2">.ApplicationPool
        ...
    }
}
</span></code></pre></div></div>

<p>I liked this better as the properties are explicitly named, so there’s no guess work about which information the property contains, but it’s still not great. One thing that I don’t have here (and really should), is validation to make sure that the passed in PSCustomObjects actually have Website, ApplicationPath, and ApplicationPool properties on them, otherwise an exception will be thrown when I try to access them. So with this approach I would still need to have documentation and validation to ensure that the client passes in a PSCustomObject with those properties.</p>

<h2 id="using-a-new-strongly-typed-object">Using a new strongly typed object</h2>

<p>I frequently read other PowerShell blog posts and recently <a href="http://blogs.technet.com/b/heyscriptingguy/archive/2013/10/19/weekend-scripter-use-powershell-and-pinvoke-to-remove-stubborn-files.aspx">stumbled across this one</a>. In the article he mentions creating a new compiled type by passing a string to the <a href="http://technet.microsoft.com/en-us/library/hh849914.aspx">Add-Type cmdlet</a>; essentially writing C# code in his PowerShell script to create a new class. I knew that you could use Add-Type to import other assemblies, but never realized that you could use it to import an assembly that doesn’t actually exist (i.e. a string in your PowerShell script). This is freaking amazing! So here is what my new solution looks like:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">   </span><span class="c"># Wrap in a try-catch in case we try to add this type twice.</span><span class="w">
</span><span class="c"># Create a class to hold an IIS Application Service's Information.</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-TypeDefinition</span><span class="w"> </span><span class="sh">@"
    using System;

    public class ApplicationServiceInformation
    {
        // The name of the Website in IIS.
        public string Website { get; set;}

        // The path to the Application, relative to the Website root.
        public string ApplicationPath { get; set; }

        // The Application Pool that the application is running in.
        public string ApplicationPool { get; set; }

        // Implicit Constructor.
        public ApplicationServiceInformation() { }

        // Explicit constructor.
        public ApplicationServiceInformation(string website, string applicationPath, string applicationPool)
        {
            this.Website = website;
            this.ApplicationPath = applicationPath;
            this.ApplicationPool = applicationPool;
        }
    }
"@</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{}</span><span class="w">

</span><span class="nv">$anotherService</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w">
</span><span class="nv">$anotherService</span><span class="o">.</span><span class="nf">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"MyWebsite"</span><span class="w">
</span><span class="nv">$anotherService</span><span class="o">.</span><span class="nf">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Version</span><span class="s2">/Payment.Services"</span><span class="w">
</span><span class="nv">$anotherService</span><span class="o">.</span><span class="nf">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Services .NET4"</span><span class="w">

</span><span class="p">[</span><span class="n">ApplicationServiceInformation</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$applicationServicesInfo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="err">(New-Object</span><span class="w"> </span><span class="err">ApplicationServiceInformation(</span><span class="s2">"MyWebsite"</span><span class="p">,</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Version</span><span class="s2">/Reporting.Services"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Services .NET4"</span><span class="p">)),</span><span class="w">
    </span><span class="p">(</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"MyWebsite"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$Version</span><span class="s2">/Core.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Services .NET4}),
    </span><span class="nv">$anotherService</span><span class="s2">,
    ...
)

function Remove-ApplicationServices
{
    param([ApplicationServiceInformation[]] </span><span class="nv">$ApplicationServicesInfo</span><span class="s2">)

    # Remove all of the Web Applications.
    foreach (</span><span class="nv">$appInfo</span><span class="s2"> in [ApplicationServiceInformation[]]</span><span class="nv">$ApplicationServicesInfo</span><span class="s2">)
    {
        </span><span class="nv">$website</span><span class="s2"> = </span><span class="nv">$appInfo</span><span class="s2">.Website
        </span><span class="nv">$appPath</span><span class="s2"> = </span><span class="nv">$appInfo</span><span class="s2">.ApplicationPath
        </span><span class="nv">$appPool</span><span class="s2"> = </span><span class="nv">$appInfo</span><span class="s2">.ApplicationPool
        ...
    }
}
</span></code></pre></div></div>

<p>I first create a simple container class to hold the application service information, and now all of my properties are explicit like with the PSCustomObject, but also I’m guaranteed the properties will exist on the object that is passed into my function. From there I declare my array of ApplicationServiceInformation objects, and the function that we can pass them into. Note that I wrap each New-Object call in parenthesis, otherwise PowerShell parses it incorrectly and will throw an error.</p>

<p>As you can see from the snippets above and below, there are several different ways that we can initialize a new instance of our ApplicationServiceInformation class:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$service1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="p">(</span><span class="s2">"Explicit Constructor"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Core.Services"</span><span class="p">,</span><span class="w"> </span><span class="s2">".NET4"</span><span class="p">)</span><span class="w">

</span><span class="nv">$service2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="p">(</span><span class="s2">"Explicit Constructor ArgumentList"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Core.Services"</span><span class="p">,</span><span class="w"> </span><span class="s2">".NET4"</span><span class="p">)</span><span class="w">

</span><span class="nv">$service3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="nx">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Using Property"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Core.Services"</span><span class="p">;</span><span class="w"> </span><span class="nx">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">".NET4"</span><span class="p">}</span><span class="w">

</span><span class="nv">$service4</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">ApplicationServiceInformation</span><span class="w">
</span><span class="nv">$service4</span><span class="o">.</span><span class="nf">Website</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Properties added individually"</span><span class="w">
</span><span class="nv">$service4</span><span class="o">.</span><span class="nf">ApplicationPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Core.Services"</span><span class="w">
</span><span class="nv">$service4</span><span class="o">.</span><span class="nf">ApplicationPool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Services .NET4"</span><span class="w">
</span></code></pre></div></div>

<h2 id="caveats">Caveats</h2>

<ul>
  <li>Note that I wrapped the call to Add-Type in a Try-Catch block. This is to prevent PowerShell from throwing an error if the type tries to get added twice. It’s sort of a hacky workaround, <a href="http://stackoverflow.com/questions/16552801/how-do-i-conditionally-add-a-class-with-add-type-typedefinition-if-it-isnt-add">but there aren’t many good alternatives</a>, since you cannot unload an assembly.</li>
  <li>This means that while developing if you make any changes to the class, <em>you’ll have to restart your PowerShell session for the changes to be applied</em>, since the Add-Type cmdlet will only work properly the first time that it is called in a session.</li>
</ul>

<p>I hope you found something in here useful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Add-Type" /><category term="array" /><category term="CSharp" /><category term="object" /><category term="PowerShell" /><category term="PSCustomObject" /><category term="strongly typed" /><summary type="html"><![CDATA[I recently read a great article that explained how to create hashtables, dictionaries, and PowerShell objects. I already knew a bit about these, but this article gives a great comparison between them, when to use each of them, and how to create them in the different versions of PowerShell.]]></summary></entry><entry><title type="html">PowerShell Functions To Delete Old Files And Empty Directories</title><link href="https://blog.danskingdom.com/powershell-functions-to-delete-old-files-and-empty-directories/" rel="alternate" type="text/html" title="PowerShell Functions To Delete Old Files And Empty Directories" /><published>2013-10-15T23:38:12+00:00</published><updated>2024-10-31T00:00:00+00:00</updated><id>https://blog.danskingdom.com/powershell-functions-to-delete-old-files-and-empty-directories</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-functions-to-delete-old-files-and-empty-directories/"><![CDATA[<p>I thought I’d share some PowerShell (PS) functions that I wrote for some clean-up scripts at work. I use these functions to delete files older than a certain date. Note that these functions require PS v3.0; slower PS v2.0 compatible functions are given at the end of this article.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Function to remove all empty directories under the given path.</span><span class="w">
</span><span class="c"># If -DeletePathIfEmpty is provided the given Path directory will also be deleted if it is empty.</span><span class="w">
</span><span class="c"># If -OnlyDeleteDirectoriesCreatedBeforeDate is provided, empty folders will only be deleted if they were created before the given date.</span><span class="w">
</span><span class="c"># If -OnlyDeleteDirectoriesNotModifiedAfterDate is provided, empty folders will only be deleted if they have not been written to after the given date.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Remove-EmptyDirectories</span><span class="p">([</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="p">)][</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Path</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DeletePathIfEmpty</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$OutputDeletedPaths</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$WhatIf</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-Directory</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-File</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">CreationTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">Sort</span><span class="nt">-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="nt">-Descending</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="c"># Sort directories to ensure we delete child directories before parent directories.</span><span class="w">
        </span><span class="n">ForEach</span><span class="nt">-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># If we should delete the given path when it is empty, and it is a directory, and it is empty, and it meets the date requirements, then delete it.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$DeletePathIfEmpty</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Container</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w">
        </span><span class="p">((</span><span class="n">Get-Item</span><span class="w"> </span><span class="nv">$Path</span><span class="p">)</span><span class="o">.</span><span class="nf">CreationTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">((</span><span class="n">Get-Item</span><span class="w"> </span><span class="nv">$Path</span><span class="p">)</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="p">))</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Function to remove all files in the given Path that were created before the given date, as well as any empty directories that may be left behind.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Remove-FilesCreatedBeforeDate</span><span class="p">([</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="p">)][</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Path</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="p">)][</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$DateTime</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DeletePathIfEmpty</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$OutputDeletedPaths</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$WhatIf</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-File</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">CreationTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="n">Remove-EmptyDirectories</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="p">:</span><span class="nv">$DeletePathIfEmpty</span><span class="w"> </span><span class="nt">-OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="nt">-OutputDeletedPaths</span><span class="p">:</span><span class="nv">$OutputDeletedPaths</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Function to remove all files in the given Path that have not been modified after the given date, as well as any empty directories that may be left behind.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Remove-FilesNotModifiedAfterDate</span><span class="p">([</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="p">)][</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Path</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="p">)][</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$DateTime</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DeletePathIfEmpty</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$OutputDeletedPaths</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$WhatIf</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-File</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="n">Remove-EmptyDirectories</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="p">:</span><span class="nv">$DeletePathIfEmpty</span><span class="w"> </span><span class="nt">-OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="nt">-OutputDeletedPaths</span><span class="p">:</span><span class="nv">$OutputDeletedPaths</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><a href="/assets/Posts/2014/01/Remove-FilesOlderThan.zip">Download File</a></p>

<p>The <strong>Remove-EmptyDirectories</strong> function removes all empty directories under the given path, and optionally (via the DeletePathIfEmpty switch) the path directory itself if it is empty after cleaning up the other directories. It also takes a couple parameters that may be specified if you only want to delete the empty directories that were created before a certain date, or that haven’t been written to since a certain date.</p>

<p>The <strong>Remove-FilesCreatedBeforeDate</strong> and <strong>Remove-FilesNotModifiedAfterDate</strong> functions are very similar to each other. They delete all files under the given path whose Created Date or Last Written To Date, respectfully, is less than the given DateTime. They then call the Remove-EmptyDirectories function with the provided date to clean up any left over empty directories.</p>

<p>To call the last 2 functions, just provide the path to the file/directory that you want it to delete if older than the given date-time. Here are some examples of calling all the functions:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Delete all files created more than 2 days ago.</span><span class="w">
</span><span class="n">Remove-FilesCreatedBeforeDate</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Some\Directory"</span><span class="w"> </span><span class="nt">-DateTime</span><span class="w"> </span><span class="p">((</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddDays</span><span class="p">(</span><span class="nt">-2</span><span class="p">))</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="w">

</span><span class="c"># Delete all files that have not been updated in 8 hours.</span><span class="w">
</span><span class="n">Remove-FilesNotModifiedAfterDate</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Another\Directory"</span><span class="w"> </span><span class="nt">-DateTime</span><span class="w"> </span><span class="p">((</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddHours</span><span class="p">(</span><span class="nt">-8</span><span class="p">))</span><span class="w">

</span><span class="c"># Delete a single file if it is more than 30 minutes old.</span><span class="w">
</span><span class="n">Remove-FilesCreatedBeforeDate</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\Another\Directory\SomeFile.txt"</span><span class="w"> </span><span class="nt">-DateTime</span><span class="w"> </span><span class="p">((</span><span class="n">Get-Date</span><span class="p">)</span><span class="o">.</span><span class="nf">AddMinutes</span><span class="p">(</span><span class="nt">-30</span><span class="p">))</span><span class="w">

</span><span class="c"># Delete all empty directories in the Temp folder, as well as the Temp folder itself if it is empty.</span><span class="w">
</span><span class="n">Remove-EmptyDirectories</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\SomePath\Temp"</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="w">

</span><span class="c"># Delete all empty directories created after Jan 1, 2014 3PM.</span><span class="w">
</span><span class="n">Remove-EmptyDirectories</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\SomePath\WithEmpty\Directories"</span><span class="w"> </span><span class="nt">-OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="w"> </span><span class="p">([</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">Parse</span><span class="p">(</span><span class="s2">"Jan 1, 2014 15:00:00"</span><span class="p">))</span><span class="w">

</span><span class="c"># See what files and directories would be deleted if we ran the command.</span><span class="w">
</span><span class="n">Remove-FilesCreatedBeforeDate</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\SomePath\Temp"</span><span class="w"> </span><span class="nt">-DateTime</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="w">

</span><span class="c"># Delete all files and directories in the Temp folder, as well as the Temp folder itself if it is empty, and output all paths that were deleted.</span><span class="w">
</span><span class="n">Remove-FilesCreatedBeforeDate</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"C:\SomePath\Temp"</span><span class="w"> </span><span class="nt">-DateTime</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Date</span><span class="p">)</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="w"> </span><span class="nt">-OutputDeletedPaths</span><span class="w">
</span></code></pre></div></div>

<p>Notice that I am using Get-Date to get the current date and time, and then <strong>subtracting</strong> the specified amount of time from it in order to get a date-time relative to the current time; you can use any valid DateTime though, such as a hard-coded date of January 1st, 2014 3PM.</p>

<p>I use these functions in some scripts that we run nightly via a scheduled task in Windows. Hopefully you find them useful too.</p>

<h2 id="powershell-v20-compatible-functions">PowerShell v2.0 Compatible Functions</h2>

<p>As promised, here are the slower PS v2.0 compatible functions. The main difference is that they use $_.PSIsContainer in the Where-Object clause rather than using the –File / –Directory Get-ChildItem switches. The Measure-Command cmdlet shows that using the switches is about 3x faster than using the where clause, but since we are talking about milliseconds here you likely won’t notice the difference unless you are traversing a large file tree (which I happen to be for my scripts that we use to clean up TFS builds).</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Function to remove all empty directories under the given path.</span><span class="w">
</span><span class="c"># If -DeletePathIfEmpty is provided the given Path directory will also be deleted if it is empty.</span><span class="w">
</span><span class="c"># If -OnlyDeleteDirectoriesCreatedBeforeDate is provided, empty folders will only be deleted if they were created before the given date.</span><span class="w">
</span><span class="c"># If -OnlyDeleteDirectoriesNotModifiedAfterDate is provided, empty folders will only be deleted if they have not been written to after the given date.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Remove-EmptyDirectories</span><span class="p">([</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)][</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Path</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DeletePathIfEmpty</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">MaxValue</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$OutputDeletedPaths</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$WhatIf</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">!</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="p">})</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">Where</span><span class="nt">-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">CreationTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">Sort</span><span class="nt">-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="nt">-Descending</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="c"># Sort directories to ensure we delete child directories before parent directories.</span><span class="w">
        </span><span class="n">ForEach</span><span class="nt">-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># If we should delete the given path when it is empty, and it is a directory, and it is empty, and it meets the date requirements, then delete it.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$DeletePathIfEmpty</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-PathType</span><span class="w"> </span><span class="nx">Container</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w">
        </span><span class="p">((</span><span class="n">Get-Item</span><span class="w"> </span><span class="nv">$Path</span><span class="p">)</span><span class="o">.</span><span class="nf">CreationTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">((</span><span class="n">Get-Item</span><span class="w"> </span><span class="nv">$Path</span><span class="p">)</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="p">))</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Function to remove all files in the given Path that were created before the given date, as well as any empty directories that may be left behind.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Remove-FilesCreatedBeforeDate</span><span class="p">([</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)][</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Path</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="p">)][</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$DateTime</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DeletePathIfEmpty</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$OutputDeletedPaths</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$WhatIf</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">!</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">CreationTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="n">Remove-EmptyDirectories</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="p">:</span><span class="nv">$DeletePathIfEmpty</span><span class="w"> </span><span class="nt">-OnlyDeleteDirectoriesCreatedBeforeDate</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="nt">-OutputDeletedPaths</span><span class="p">:</span><span class="nv">$OutputDeletedPaths</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Function to remove all files in the given Path that have not been modified after the given date, as well as any empty directories that may be left behind.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Remove-FilesNotModifiedAfterDate</span><span class="p">([</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)][</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Path</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="p">)][</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$DateTime</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$DeletePathIfEmpty</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$OutputDeletedPaths</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$WhatIf</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">!</span><span class="bp">$_</span><span class="o">.</span><span class="nf">PSIsContainer</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">LastWriteTime</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w">
        </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$OutputDeletedPaths</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Output</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">FullName</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="n">Remove-EmptyDirectories</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="w"> </span><span class="nt">-DeletePathIfEmpty</span><span class="p">:</span><span class="nv">$DeletePathIfEmpty</span><span class="w"> </span><span class="nt">-OnlyDeleteDirectoriesNotModifiedAfterDate</span><span class="w"> </span><span class="nv">$DateTime</span><span class="w"> </span><span class="nt">-OutputDeletedPaths</span><span class="p">:</span><span class="nv">$OutputDeletedPaths</span><span class="w"> </span><span class="nt">-WhatIf</span><span class="p">:</span><span class="nv">$WhatIf</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><a href="/assets/Posts/2014/01/Remove-FilesOlderThanPSv2.zip">Download PSv2 File</a></p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Date" /><category term="Delete" /><category term="Directories" /><category term="directory" /><category term="file" /><category term="Files" /><category term="Old" /><category term="Older" /><category term="PowerShell" /><category term="Remove" /><category term="Time" /><summary type="html"><![CDATA[I thought I’d share some PowerShell (PS) functions that I wrote for some clean-up scripts at work. I use these functions to delete files older than a certain date. Note that these functions require PS v3.0; slower PS v2.0 compatible functions are given at the end of this article.]]></summary></entry><entry><title type="html">Windows Phone Developers: Do not renew your subscription until the expiry DAY or else Microsoft steals your money</title><link href="https://blog.danskingdom.com/windows-phone-developers-do-not-renew-your-subscription-until-the-expiry-day-or-else-microsoft-steals-your-money/" rel="alternate" type="text/html" title="Windows Phone Developers: Do not renew your subscription until the expiry DAY or else Microsoft steals your money" /><published>2013-10-01T23:36:46+00:00</published><updated>2013-10-01T23:36:46+00:00</updated><id>https://blog.danskingdom.com/windows-phone-developers-do-not-renew-your-subscription-until-the-expiry-day-or-else-microsoft-steals-your-money</id><content type="html" xml:base="https://blog.danskingdom.com/windows-phone-developers-do-not-renew-your-subscription-until-the-expiry-day-or-else-microsoft-steals-your-money/"><![CDATA[<h2 id="the-problem">The Problem</h2>

<p>So as I found out today, if you renew your Windows Phone Developer subscription early, it does not renew it for a year from the expiry date, <em>it renews it for a year from the date you paid to have it renewed</em>. So essentially you pay for a 12 month subscription, but receive less than 12 months. I’m not sure if the Windows Store subscription has the same problem or not, but beware.</p>

<h2 id="the-story">The Story</h2>

<p>After this happened I started up a support request chat with MS to have them extend the expiry date to what it should be, but was told that they are not able to do this. Here is our chat transcript:</p>

<table cellspacing="0" cellpadding="0" border="1">
  <tr>
    <td>
      <p>
        <b>General Info</b><b></b>
      </p>
    </td>
  </tr>

  <tr>
    <td>
      <p>
        <b>Chat start time</b>
      </p>
    </td>

    <td>
      <p>
        Oct 1, 2013 6:16:27 PM EST
      </p>
    </td>
  </tr>

  <tr>
    <td>
      <p>
        <b>Chat end time</b>
      </p>
    </td>

    <td>
      <p>
        Oct 1, 2013 6:40:49 PM EST
      </p>
    </td>
  </tr>

  <tr>
    <td>
      <p>
        <b>Duration (actual chatting time)</b>
      </p>
    </td>

    <td>
      <p>
        00:24:21
      </p>
    </td>
  </tr>

  <tr>
    <td>
      <p>
        <b>Operator</b>
      </p>
    </td>

    <td>
      <p>
        Adrian
      </p>
    </td>
  </tr>
</table>

<table cellspacing="0" cellpadding="0" border="1">
  <tr>
    <td>
      <p>
        <b>Chat Transcript</b>
      </p>
    </td>
  </tr>

  <tr>
    <td>
      <p>
        info: Please wait for an agent to respond. You are currently '1' in the queue. <br />info: <a href="http://go.microsoft.com/fwlink/?LinkId=81184&amp;clcid=0x809">Privacy Statement</a> <br />You are now chatting with 'Adrian'. <br />Adrian: Hello, Dan <br /><font style="background-color: #ffffff" color="#0000ff">Dan</font>: Hi there, I just renewed my Windows Phone Developer subscription today <br /><font color="#0000ff">Dan</font>: My old expiry date was 10/24/2013, but when I renewed it the new expiry date is 10/1/2014 <br /><font color="#0000ff">Dan</font>: but it should be 10/24/2014 <br /><font color="#ff0000">Adrian</font>: So I understand that you have renewed your subscription before the expiration date and it seems that you have lost several days on your subscription. <br /><font color="#0000ff">Dan</font>: yup, that's basically it <br /><font color="#0000ff">Dan</font>: I got the email notification about it expiring soon today, so I thought I would do it now before I forgot about it <br /><font color="#ff0000">Adrian</font>: As it turns out, renewing your subscription manually is only available within 30 days of the expiration, but currently it does not stack the subscription. <br /><font color="#ff0000">Adrian</font>: We recommend that you wait closer to your renewal date or let the account auto-renew (on by default) so that you do not lose any days. <br /><font color="#0000ff">Dan</font>: so can you manually adjust my expiration date to be 10/24/2014 like it should, and submit a bug for them to fix that? <br /><font color="#ff0000">Adrian</font>: I apologize for the inconvenience since I currently do not have a way to extend the subscription or modify the expiration date. Our engineers are already aware of the renewal behavior, but there is no estimated date on when a change will be implemented. <br /><font color="#0000ff">Dan</font>: so can you guys credit my credit card for the difference then? You know that's essentially jut stealing then... <br /><font color="#0000ff">Dan</font>: Or just escalate me to a supervisor/manager/engineer who does have the ability to change the expiration date? <br /><font color="#ff0000">Adrian</font>: I apologize as there is not a way to modify the expiration date within the system. My team works here as peers so there is no escalation path. The prorated amount for 24 days would amount to 1.28 USD out of 19 USD for a years subscription however we do not offer partial refunds per Microsoft policy. <br /><font color="#ff0000">Adrian</font>: At best, I can refund the full amount and cancel your current subscription. <br /><font color="#ff0000">Adrian</font>: If there is any other way that I can offer my assistance I will be glad to help. <br /><font color="#ff0000">Adrian</font>: Hello, are you there? I have not received any response from you. <br /><font color="#ff0000">Adrian</font>: I have not yet received any response from you. I'll wait for a minute before closing this chat session. Please feel free to initiate a new chat session so that we can assist you further. <br /><font color="#ff0000">Adrian</font>: For some reason, possibly due to technical difficulty, I have not received a response from you. I will update the case notes and end this session. Please feel free to initiate a new chat session so that we can assist you further. Thank you for contacting MarketPlace Chat Support. Have a great day!
      </p>
    </td>
  </tr>
</table>

<p>I started this chat up while at work and unfortunately had to leave my desk for 15 minutes, so Adrian closed our chat before I could reply. The Windows Phone Developer subscription is now only $19/year, but I actually used a promo code that I received many months ago when it was still $100/year; so while Adrian mentioned that the missed days only added up to $1.28, the cost would actually be closer to $10.28 for the people who gave me the promo code. Also, by this time next year the price may go back up to $100/year, in which case I’ll be forking over the $10.28 to pay for the month of October 2014.</p>

<p>Also, while Adrian admits that “Our engineers are already aware of the renewal behavior, but there is no estimated date on when a change will be implemented.”, this behaviour is not stated anywhere on the web page when renewing your account. This seems pretty irresponsible to me, especially when it directly affects payments. Can you imagine if your internet provider was allowed to just charge you for an extra month of service without warning or agreement.</p>

<p>Adrian mentioned that the recommended thing to do is to just let your annual subscription auto-renew. This is likely the ideal situation, but I’m often paranoid that automatic transactions won’t go through and will be unnoticed (I’ve been bit by this in the past), or that by the time the renewal comes around my credit card info will have changed, etc., so I often manually renew my annual subscriptions. Especially when the consequence of not renewing your membership means your apps are removed from the store and you stop making money off of them. MS is basically stealing money from those who choose to manually renew their subscription.</p>

<p>I’m not going to bother pursuing this with MS as $10 isn’t worth the time or stress, but I wanted to try and let others know so that you don’t get burned as well.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows Phone" /><category term="Developer" /><category term="Expiry" /><category term="Microsoft" /><category term="Subscription" /><category term="Windows Phone" /><summary type="html"><![CDATA[The Problem]]></summary></entry><entry><title type="html">Windows Live Writer Post Fails With Error “The underlying connection was closed: An unexpected error occurred on a receive.”</title><link href="https://blog.danskingdom.com/wlw-post-fails-with-error-the-underlying-connection-was-closed-an-unexpected-error-occurred-on-a-receive/" rel="alternate" type="text/html" title="Windows Live Writer Post Fails With Error “The underlying connection was closed: An unexpected error occurred on a receive.”" /><published>2013-09-27T18:34:41+00:00</published><updated>2013-09-27T18:34:41+00:00</updated><id>https://blog.danskingdom.com/wlw-post-fails-with-error-the-underlying-connection-was-closed-an-unexpected-error-occurred-on-a-receive</id><content type="html" xml:base="https://blog.danskingdom.com/wlw-post-fails-with-error-the-underlying-connection-was-closed-an-unexpected-error-occurred-on-a-receive/"><![CDATA[<p>When trying to upload <a href="https://blog.danskingdom.com/launch-visual-studio-checkin-window-with-a-keystroke/">my last blog post</a> from Windows Live Writer (WLW) to WordPress (WP) I received the following error:</p>

<blockquote>
  <p>Network Connection Error
Error attempting to connect to blog at:
<a href="https://blog.danskingdom.com/xmlrpc.php">https://blog.danskingdom.com/xmlrpc.php</a>
The underlying connection was closed. An unexpected error occurred on a receive.</p>
</blockquote>

<p><img src="/assets/Posts/2013/09/WLWNetworkConnectionError.png" alt="WLW Network Connection Error" /></p>

<p>I had no problem uploading to my blog a couple weeks earlier and hadn’t done any updates or changed anything, so I thought this was strange. After waiting a day, thinking maybe GoDaddy (my WP host) was having issues, I was still getting the error. After Googling I found many others reporting this error with varying degrees of success fixing it. So after trying some suggestions that worked for others (change WLW blog URL from http to https, edit the WP xmlrpc.php file, delete and recreate blog account in WLW, reboot, etc.) I was still getting this same error.</p>

<p>So I decided to try posting a new “test” post, and low and behold it worked. So it appeared the problem was something with the content of my article. So I started removing chunks of content from the article and trying to post. Eventually I found that the problem was being caused by the string “In that post” in the first paragraph of the post. I thought that maybe some weird hidden characters maybe got in there somehow, but after reviewing the article’s Source I could see that it was just plain old text. I deleted the sentence and retyped it, but it still didn’t work. If I just removed “In that post” from the sentence then everything worked fine; very strange After more playing around, I found that if I just added a comma to the end and made it “In that post,”, that also fixed the problem. So that’s how I’ve left it.</p>

<p>I don’t know what is special about the string “In that post”; I created another test article with that string in it and was able to post it without any problems. Just a weird one-off WLW-WP problem I guess.</p>

<h2 id="moral-of-the-story">Moral of the story</h2>

<p>If you run into this same error, before you go muddling with config files and recreating your blog account, just try posting a quick “test” article. If it works, then the problem is somewhere in your article’s content, so start stripping pieces away until you are able to get it to post successfully and narrow down the culprit. Also, if you don’t want to publish a half-baked article while you are tracking down the problem, you can do a Save Post Draft To Blog instead of a full Publish to see if you are still getting the error</p>

<p>Happy coding!</p>

<h2 id="update">Update</h2>

<p>I’ve ran into this problem again when trying to post <a href="https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin/">this</a> article. 3 different spots in the article were causing the problem. Here is the source of the article with what broke it, and what worked:</p>

<ol>
  <li>
    <p>This broke:</p>

    <blockquote>
      <p>&lt;li&gt;Click Yes when prompted to &lt; strong &gt; Run With UI Access &lt; / strong &gt; . &lt;/li&gt;</p>
    </blockquote>

    <p>(I had to add spaces around all of the 3 characters &lt;, &gt;, and / in the strong tags to get it to post here)</p>

    <p>This worked:</p>

    <blockquote>
      <p>&lt;li&gt;Click Yes when prompted to Run With UI Access.&lt;/li&gt;</p>
    </blockquote>
  </li>
  <li>
    <p>This broke:</p>

    <blockquote>
      <p>&lt;p&gt;Today I stumbled across <a href="&lt;http://www.autohotkey.com/board/topic/70449-enable-interaction-with-administrative-programs/">&gt;this post on the AHK community forums &lt; / a &gt; .</a></p>
    </blockquote>

    <p>(I had to add spaces around the each character of the closing &lt;/a&gt; tag to get it to post here)</p>

    <p>This worked:</p>

    <blockquote>
      <p>&lt;p&gt;Today I stumbled across <a href="&lt;http://www.autohotkey.com/board/topic/70449-enable-interaction-with-administrative-programs/">&gt;this post</a> on the AHK community forums.</p>
    </blockquote>
  </li>
  <li>
    <p>This broke:</p>

    <blockquote>
      <p>the &lt;a href=”<a href="http://www.autohotkey.com/docs/commands/RunAs.htm&quot;">http://www.autohotkey.com/docs/commands/RunAs.htm"</a>&gt;RunAs command &lt; / a &gt; .&lt;/p&gt;</p>
    </blockquote>

    <p>(Again, I had to add spaces around each character in the closing &lt;/a&gt; tag to get it to post here)</p>

    <p>This worked:</p>

    <blockquote>
      <p>the &lt;a href=”<a href="http://www.autohotkey.com/docs/commands/RunAs.htm&quot;">http://www.autohotkey.com/docs/commands/RunAs.htm"</a>&gt;RunAs&lt;/a&gt; command.&lt;/p&gt;</p>
    </blockquote>
  </li>
</ol>

<p>I can reproduce this issue every time on that article, and also on this one (which is why I had to change the problem code slightly so I could get it to post here). So unlike my first encounter with this problem, these ones all seem to be problems parsing html markup tags; specifically the &lt;/&gt; characters. I’m not sure if this is a problem with Windows Live Writer or WordPress, but it is definitely a frustrating bug. I’m running Windows 8 x64 and the latest versions of WLW and WP.</p>

<p>If you have any thoughts please comment below.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows Live Writer" /><category term="WordPress" /><category term="error" /><category term="Network Connection Error" /><category term="Windows Live Writer" /><category term="WLW" /><category term="WordPress" /><summary type="html"><![CDATA[When trying to upload my last blog post from Windows Live Writer (WLW) to WordPress (WP) I received the following error:]]></summary></entry><entry><title type="html">Launch Visual Studio Checkin Window With A Keystroke</title><link href="https://blog.danskingdom.com/launch-visual-studio-checkin-window-with-a-keystroke/" rel="alternate" type="text/html" title="Launch Visual Studio Checkin Window With A Keystroke" /><published>2013-09-27T17:18:30+00:00</published><updated>2013-09-27T17:18:30+00:00</updated><id>https://blog.danskingdom.com/launch-visual-studio-checkin-window-with-a-keystroke</id><content type="html" xml:base="https://blog.danskingdom.com/launch-visual-studio-checkin-window-with-a-keystroke/"><![CDATA[<p>A few weeks ago I blogged about <a href="https://blog.danskingdom.com/getting-custom-tfs-checkin-policies-to-work-when-committing-from-the-command-line-i-e-tf-checkin/">how you can get custom TFS checkin policies to work when committing from the command line</a>. In that post, I had a quick aside about how you can launch the checkin window (i.e. pending changes) with a quick keystroke using <a href="http://www.autohotkey.com/">AutoHotkey</a>. I realize that many people don’t use AutoHotkey (although you really should; it can save you a lot of time), so I thought I would show how you can accomplish this task without AutoHotkey. It’s quite simple really, and it allows you to launch the VS Checkin window from anywhere, even if you don’t have Visual Studio open.</p>

<h2 id="steps-to-launch-vs-checkin-window-from-the-visual-studio-command-prompt">Steps To Launch VS Checkin Window From The Visual Studio Command Prompt</h2>

<ol>
  <li>Open the Visual Studio Command Prompt. To do this, just hit the windows key and type <code class="language-plaintext highlighter-rouge">Developer Command Prompt For VS2012</code> if using VS 2012, or <code class="language-plaintext highlighter-rouge">Visual Studio Command Prompt (2010)</code> if using VS 2010.</li>
  <li>In the VS Command Prompt, change to a directory that is in your TFS workspace mapping. e.g. <code class="language-plaintext highlighter-rouge">cd C:\Dev\TFS</code></li>
  <li>Type <code class="language-plaintext highlighter-rouge">tf checkin</code> and hit enter.</li>
</ol>

<h2 id="steps-to-launch-vs-checkin-window-with-a-shortcut-key">Steps To Launch VS Checkin Window With A Shortcut Key</h2>

<ol>
  <li>Right click on your desktop and choose New –&gt; Shortcut to create a new shortcut file.
<img src="/assets/Posts/2013/09/CreateShortcutOnDesktop1.png" alt="Create Shortcut On Desktop" /></li>
  <li>
    <p>Have the shortcut point to the TF executable. This can be found at “C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\TF.exe”.</p>

    <p><img src="/assets/Posts/2013/09/PathToTfExeForShortcut.png" alt="PathToTfExeForShortcut" /></p>
  </li>
  <li>Enter the name for your shortcut file, such as VS Checkin.
 <img src="/assets/Posts/2013/09/NameShortcut.png" alt="Name Shortcut" /></li>
  <li>Now that the shortcut has been created on your desktop, right-click on it and go the the properties (or use alt+enter). You will want to:
    <ul>
      <li>add <code class="language-plaintext highlighter-rouge">checkin</code> to the very end of the Target,</li>
      <li>change the Start In directory to a directory you have mapped in your TFS workspace,</li>
      <li>and assign it a Shortcut Key.</li>
    </ul>

    <p><img src="/assets/Posts/2013/10/CheckinShortcutPropertiesToChange.png" alt="Checkin Shortcut Properties To Change" /></p>
  </li>
</ol>

<h2 id="results">Results</h2>

<p>That’s it. Go ahead and try your shortcut key. I’ll wait. You should see something like this:</p>

<p><img src="/assets/Posts/2013/09/VsCheckinWindow.png" alt="Visual Studio Checkin Window" /></p>

<p>Notice that it pops both a command prompt window and the actual checkin window. If you don’t have any pending changes, then the command prompt window will simply open and close.</p>

<h2 id="more-information-and-caveats">More Information and Caveats</h2>

<h3 id="old-style-checkin-window">Old Style Checkin Window</h3>

<p>You probably noticed in the screenshot above that even though I’m using Visual Studio 2012, it still pops the old VS 2010 style of checkin window. I actually prefer this popped out window to the VS 2012 pending changes pane, and I know <a href="http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2654486-vs11-bring-back-the-old-pending-changes-window">a lot of people agree</a>.</p>

<h3 id="getting-the-shortcut-off-your-desktop">Getting The Shortcut Off Your Desktop</h3>

<p>Above I had you create the shortcut on your desktop, but you might not want to have it clutter up your desktop. Unfortunately if you move it to some other folder you will find that the shortcut key no longer works. For some reason <strong>I have found that for the shortcut key to work the shortcut file must either be on your desktop, or in the Start Menu</strong>. If you are using Windows 7 you can simply drag and drop the shortcut into the Programs section of the Start Menu. For us Windows 8.0 folks the Start Menu is gone, so just manually move the shortcut to <strong>“C:\ProgramData\Microsoft\Windows\Start Menu\Programs”</strong>.</p>

<p>You may find that after moving the shortcut file the shortcut key no longer works. You just have to go back into the Shortcut file properties, assign it a new Shortcut Key, hit Apply, and then assign the original Shortcut Key back.</p>

<h3 id="shelving-changes-in-tfs-using-the-old-style-shelve-window">Shelving Changes In TFS Using The Old Style Shelve Window</h3>

<p>If you are using TFS and want to shelve your changes instead of checking them in, you can access the old Shelve Pending Changes window in the same way. In step 4 above, instead of adding <code class="language-plaintext highlighter-rouge">checkin</code> as the TF.exe argument, add <code class="language-plaintext highlighter-rouge">shelve</code>. To launch it from the Visual Studio Command Prompt, type <code class="language-plaintext highlighter-rouge">tf shelve</code> instead of <code class="language-plaintext highlighter-rouge">tf checkin</code>.</p>

<h3 id="getting-custom-tfs-checkin-policies-to-work">Getting Custom TFS Checkin Policies To Work</h3>

<p>As you may have guessed from the very start of the article, custom TFS checkin policies don’t run in this checkin window by default; they will throw errors. Fortunately for you I have already created <a href="https://blog.danskingdom.com/getting-custom-tfs-checkin-policies-to-work-when-committing-from-the-command-line-i-e-tf-checkin/">a registry file that you can run after each checkin policy update</a> that you do which will rectify this problem.</p>

<p>I hope this helps you be more productive. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="TFS" /><category term="Visual Studio" /><category term="Checkin" /><category term="Commit" /><category term="Hotkey" /><category term="Key" /><category term="keyboard shortcuts" /><category term="Shelve" /><category term="Shortcut" /><category term="TFS" /><category term="Visual Studio" /><summary type="html"><![CDATA[A few weeks ago I blogged about how you can get custom TFS checkin policies to work when committing from the command line. In that post, I had a quick aside about how you can launch the checkin window (i.e. pending changes) with a quick keystroke using AutoHotkey. I realize that many people don’t use AutoHotkey (although you really should; it can save you a lot of time), so I thought I would show how you can accomplish this task without AutoHotkey. It’s quite simple really, and it allows you to launch the VS Checkin window from anywhere, even if you don’t have Visual Studio open.]]></summary></entry><entry><title type="html">Have Your NuGet Package Install Itself As A Development Dependency</title><link href="https://blog.danskingdom.com/have-your-nuget-package-install-itself-as-a-development-dependency/" rel="alternate" type="text/html" title="Have Your NuGet Package Install Itself As A Development Dependency" /><published>2013-09-18T23:30:26+00:00</published><updated>2013-09-18T23:30:26+00:00</updated><id>https://blog.danskingdom.com/have-your-nuget-package-install-itself-as-a-development-dependency</id><content type="html" xml:base="https://blog.danskingdom.com/have-your-nuget-package-install-itself-as-a-development-dependency/"><![CDATA[<h2 id="the-problem">The Problem</h2>

<p>I recently <a href="https://blog.danskingdom.com/automatically-create-your-projects-nuget-package-every-time-it-builds-via-nuget/">blogged about</a> a <a href="https://www.nuget.org/packages/CreateNewNuGetPackageFromProjectAfterEachBuild/">NuGet package I made</a> that allows you to easily turn your own projects into a NuGet package, making it easy to share your work with the world.  One problem I ran into with this was that if somebody used my NuGet package to create their package, their NuGet package listed my NuGet package as a dependency.  This meant that when they distributed their package to others, it would install both their package and mine.  Obviously this is undesirable, since their library has no dependency on my package; my package was meant purely to help them with the development process.</p>

<p>Unfortunately there wasn’t much I could do about this; that is, until the release of <a href="https://nuget.codeplex.com/">NuGet 2.7</a> which came out a few weeks ago.  You can see from <a href="http://blog.nuget.org/20130814/nuget-2.7-release-candidate.html">the release notes</a> that they added a new <code class="language-plaintext highlighter-rouge">developmentDependency</code> attribute that can be used.  This made things a bit better because it allowed users who installed my package to go into their project’s <code class="language-plaintext highlighter-rouge">packages.config</code> file, find the element corresponding to my package, and add the <code class="language-plaintext highlighter-rouge">developmentDependency="true"</code> attribute to it.</p>

<p>So this was better, but still kinda sucked because it required users to do this step manually, and most of them likely aren’t even aware of the problem or that there was a fix for it.  When users (and myself) install a package they want it to just work; which is why I created a fix for this.</p>

<h2 id="the-fix">The Fix</h2>

<p><strong>Update</strong> - As of NuGet 2.8 there is a built-in way to do the fix below. <a href="http://stackoverflow.com/a/24216882/602585">See this post for more info</a>.</p>

<p>The nice thing about NuGet packages is that you can define PowerShell scripts that can run when users install and uninstall your packages, as is <a href="http://docs.nuget.org/docs/creating-packages/creating-and-publishing-a-package">documented near the bottom of this page</a>.  I’ve created a PowerShell script that will automatically go in and adjust the project’s packages.config file to mark your package as a development dependency.  This means there is no extra work for the user to do.</p>

<p>The first thing you need to do (if you haven’t already) is include an <code class="language-plaintext highlighter-rouge">Install.ps1</code> script in your NuGet package’s .nuspec file.  If you don’t currently use a .nuspec file, check out <a href="http://docs.nuget.org/docs/creating-packages/creating-and-publishing-a-package">this page for more information</a>.  I also include a sample .nuspec file at the end of this post for reference.  The line to add to your .nuspec file will look something like this:</p>

<blockquote>
  <p>&lt;file src=”NuGetFiles\Install.ps1” target=”tools\Install.ps1” /&gt;</p>
</blockquote>

<p>and then the contents of Install.ps1 should look like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">param</span><span class="p">(</span><span class="nv">$installPath</span><span class="p">,</span><span class="w"> </span><span class="nv">$toolsPath</span><span class="p">,</span><span class="w"> </span><span class="nv">$package</span><span class="p">,</span><span class="w"> </span><span class="nv">$project</span><span class="p">)</span><span class="w">

</span><span class="c"># Edits the project's packages.config file to make sure the reference to the given package uses the developmentDependency="true" attribute.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Set-PackageToBeDevelopmentDependency</span><span class="p">(</span><span class="nv">$PackageId</span><span class="p">,</span><span class="w"> </span><span class="nv">$ProjectDirectoryPath</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlNamespaceManager</span><span class="p">(</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># If a Namespace URI was not given, use the Xml document's default namespace.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$NamespaceURI</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">DocumentElement</span><span class="o">.</span><span class="nf">NamespaceURI</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="c"># In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up.</span><span class="w">
        </span><span class="p">[</span><span class="n">System.Xml.XmlNamespaceManager</span><span class="p">]</span><span class="nv">$xmlNsManager</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Xml.XmlNamespaceManager</span><span class="p">(</span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">NameTable</span><span class="p">)</span><span class="w">
        </span><span class="nv">$xmlNsManager</span><span class="o">.</span><span class="nf">AddNamespace</span><span class="p">(</span><span class="s2">"ns"</span><span class="p">,</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="p">)</span><span class="w">
        </span><span class="kr">return</span><span class="w"> </span><span class="p">,</span><span class="nv">$xmlNsManager</span><span class="w">   </span><span class="c"># Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[].</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-FullyQualifiedXmlNodePath</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">return</span><span class="w"> </span><span class="s2">"/ns:</span><span class="si">$(</span><span class="nv">$NodePath</span><span class="o">.</span><span class="nf">Replace</span><span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="nv">$NodeSeparatorCharacter</span><span class="si">)</span><span class="s2">, '/ns:'))"</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlNodes</span><span class="p">(</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$xmlNsManager</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNamespaceManager</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$fullyQualifiedNodePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-FullyQualifiedXmlNodePath</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$NodePath</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

        </span><span class="c"># Try and get the nodes, then return them. Returns $null if no nodes were found.</span><span class="w">
        </span><span class="nv">$nodes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">SelectNodes</span><span class="p">(</span><span class="nv">$fullyQualifiedNodePath</span><span class="p">,</span><span class="w"> </span><span class="nv">$xmlNsManager</span><span class="p">)</span><span class="w">
        </span><span class="kr">return</span><span class="w"> </span><span class="nv">$nodes</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Get the path to the project's packages.config file.</span><span class="w">
    </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Project directory is '</span><span class="nv">$ProjectDirectoryPath</span><span class="s2">'."</span><span class="w">
    </span><span class="nv">$packagesConfigFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$ProjectDirectoryPath</span><span class="w"> </span><span class="s2">"packages.config"</span><span class="w">

    </span><span class="c"># If we found the packages.config file, try and update it.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$packagesConfigFilePath</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Found packages.config file at '</span><span class="nv">$packagesConfigFilePath</span><span class="s2">'."</span><span class="w">

        </span><span class="c"># Load the packages.config xml document and grab all of the &lt;package&gt; elements.</span><span class="w">
        </span><span class="nv">$xmlFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Xml.XmlDocument</span><span class="w">
        </span><span class="nv">$xmlFile</span><span class="o">.</span><span class="nf">Load</span><span class="p">(</span><span class="nv">$packagesConfigFilePath</span><span class="p">)</span><span class="w">
        </span><span class="nv">$packageElements</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNodes</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$xmlFile</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="s2">"packages.package"</span><span class="w">

        </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Packages.config contents before modification are:</span><span class="se">`n</span><span class="si">$(</span><span class="nv">$xmlFile</span><span class="o">.</span><span class="nf">InnerXml</span><span class="si">)</span><span class="s2">"</span><span class="w">

        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$packageElements</span><span class="p">))</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Could not find any &lt;package&gt; elements in the packages.config xml file '</span><span class="nv">$packagesConfigFilePath</span><span class="s2">'."</span><span class="w">
            </span><span class="kr">return</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># Add the developmentDependency attribute to the NuGet package's entry.</span><span class="w">
        </span><span class="nv">$packageElements</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">id</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$PackageId</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">SetAttribute</span><span class="p">(</span><span class="s2">"developmentDependency"</span><span class="p">,</span><span class="w"> </span><span class="s2">"true"</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w">

        </span><span class="c"># Save the packages.config file back now that we've changed it.</span><span class="w">
        </span><span class="nv">$xmlFile</span><span class="o">.</span><span class="nf">Save</span><span class="p">(</span><span class="nv">$packagesConfigFilePath</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="c"># Else we couldn't find the packages.config file for some reason, so error out.</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Could not find packages.config file at '</span><span class="nv">$packagesConfigFilePath</span><span class="s2">'."</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Set this NuGet Package to be installed as a Development Dependency.</span><span class="w">
</span><span class="n">Set-PackageToBeDevelopmentDependency</span><span class="w"> </span><span class="nt">-PackageId</span><span class="w"> </span><span class="nv">$package</span><span class="o">.</span><span class="nf">Id</span><span class="w"> </span><span class="nt">-ProjectDirectoryPath</span><span class="w"> </span><span class="p">([</span><span class="n">System.IO.Directory</span><span class="p">]::</span><span class="n">GetParent</span><span class="p">(</span><span class="nv">$project</span><span class="o">.</span><span class="nf">FullName</span><span class="p">))</span><span class="w">
</span></code></pre></div></div>

<p><a href="/assets/Posts/2013/09/Install.zip">Download Script File</a></p>

<p>And that’s it.  Basically this script will be ran after your package is installed, and it will parse the project’s packages.config xml file looking for the element with your package’s ID, and then it will add the developmentDependency=”true” attribute to that element.  And of course, if you want to add more code to the end of the file to do additional work, go ahead.</p>

<p>So now your users won’t have to manually edit their packages.config file, and your user’s users won’t have additional, unnecessary dependencies installed.</p>

<h2 id="more-info">More Info</h2>

<p>As promised, here is a sample .nuspec file for those of you that are not familiar with them and what they should look like.  This is actually the .nuspec file I use for my package mentioned at the start of this post.  You can see that I include the Install.ps1 file near the bottom of the file.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;package</span> <span class="na">xmlns=</span><span class="s">"http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;metadata&gt;</span>
    <span class="nt">&lt;id&gt;</span>CreateNewNuGetPackageFromProjectAfterEachBuild<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.4.2<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;title&gt;</span>Create New NuGet Package From Project After Each Build<span class="nt">&lt;/title&gt;</span>
    <span class="nt">&lt;authors&gt;</span>Daniel Schroeder,iQmetrix<span class="nt">&lt;/authors&gt;</span>
    <span class="nt">&lt;owners&gt;</span>Daniel Schroeder,iQmetrix<span class="nt">&lt;/owners&gt;</span>
    <span class="nt">&lt;licenseUrl&gt;</span>https://newnugetpackage.codeplex.com/license<span class="nt">&lt;/licenseUrl&gt;</span>
    <span class="nt">&lt;projectUrl&gt;</span>https://newnugetpackage.codeplex.com/wikipage?title=NuGet%20Package%20To%20Create%20A%20NuGet%20Package%20From%20Your%20Project%20After%20Every%20Build<span class="nt">&lt;/projectUrl&gt;</span>
    <span class="nt">&lt;requireLicenseAcceptance&gt;</span>false<span class="nt">&lt;/requireLicenseAcceptance&gt;</span>
    <span class="nt">&lt;description&gt;</span>Automatically creates a NuGet package from your project each time it builds. The NuGet package is placed in the project's output directory.
    If you want to use a .nuspec file, place it in the same directory as the project's project file (e.g. .csproj, .vbproj, .fsproj).
    This adds a PostBuildScripts folder to your project to house the PowerShell script that is called from the project's Post-Build event to create the NuGet package.
    If it does not seem to be working, check the Output window for any errors that may have occurred.<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;summary&gt;</span>Automatically creates a NuGet package from your project each time it builds.<span class="nt">&lt;/summary&gt;</span>
    <span class="nt">&lt;releaseNotes&gt;</span>Updated to use latest version of New-NuGetPackage.ps1.<span class="nt">&lt;/releaseNotes&gt;</span>
    <span class="nt">&lt;copyright&gt;</span>Daniel Schroeder 2013<span class="nt">&lt;/copyright&gt;</span>
    <span class="nt">&lt;tags&gt;</span>Auto Automatic Automatically Build Pack Create New NuGet Package From Project After Each Build On PowerShell Power Shell .nupkg new nuget package NewNuGetPackage New-NuGetPackage<span class="nt">&lt;/tags&gt;</span>
  <span class="nt">&lt;/metadata&gt;</span>
  <span class="nt">&lt;files&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"..\New-NuGetPackage.ps1"</span> <span class="na">target=</span><span class="s">"content\PostBuildScripts\New-NuGetPackage.ps1"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"Content\NuGet.exe"</span> <span class="na">target=</span><span class="s">"content\PostBuildScripts\NuGet.exe"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"Content\BuildNewPackage-RanAutomatically.ps1"</span> <span class="na">target=</span><span class="s">"content\PostBuildScripts\BuildNewPackage-RanAutomatically.ps1"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"Content\UploadPackage-RunManually.ps1"</span> <span class="na">target=</span><span class="s">"content\PostBuildScripts\UploadPackage-RunManually.ps1"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"Content\UploadPackage-RunManually.bat"</span> <span class="na">target=</span><span class="s">"content\PostBuildScripts\UploadPackage-RunManually.bat"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"tools\Install.ps1"</span> <span class="na">target=</span><span class="s">"tools\Install.ps1"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"tools\Uninstall.ps1"</span> <span class="na">target=</span><span class="s">"tools\Uninstall.ps1"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/files&gt;</span>
<span class="nt">&lt;/package&gt;</span>
</code></pre></div></div>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="NuGet" /><category term="PowerShell" /><category term="Dependency" /><category term="developmentDependency" /><category term="Install.ps1" /><category term="NuGet" /><category term="nuspec" /><category term="Package" /><category term="PowerShell" /><summary type="html"><![CDATA[The Problem]]></summary></entry><entry><title type="html">PowerShell Needs A Centralized Package Management Solution</title><link href="https://blog.danskingdom.com/powershell-needs-a-centralized-package-management-solution/" rel="alternate" type="text/html" title="PowerShell Needs A Centralized Package Management Solution" /><published>2013-09-09T20:20:06+00:00</published><updated>2013-09-09T20:20:06+00:00</updated><id>https://blog.danskingdom.com/powershell-needs-a-centralized-package-management-solution</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-needs-a-centralized-package-management-solution/"><![CDATA[<p><strong>TL;DR</strong> - PowerShell needs centralized package management. Please <a href="https://connect.microsoft.com/PowerShell/feedback/details/800050/centralized-package-management-for-powershell">go up-vote this request to have it added to PowerShell</a>.</p>

<hr />

<p>I love PowerShell, and I love writing reusable PowerShell modules. They work great when I am writing scripts for myself. The problem comes in when I write a script that depends on some modules, and I then want to share that script with others. I basically have 2 options:</p>

<ol>
  <li>Track down all of the module files that the script depends on, zip them all up, and send them to the recipient along with instructions such as, “Navigate to this folder on your PC, create a new folder with this name, copy file X to this location, rinse, repeat…”.</li>
  <li>Track down all of the module files that the script depends on and copy-paste their contents directly into the top of the script file, so I just send the user one very large file.</li>
</ol>

<p>Neither of these solutions are ideal. Maybe I’m missing something? In my opinion, PowerShell really needs centralized package management; something similar to <a href="http://rubygems.org/">Ruby Gems</a> would be great. Basically a website where users can upload their scripts with a unique ID, and then in their PowerShell script at the top of the file just list the modules that the script depends on. If the modules are not installed on that PC yet, then they would automatically be downloaded and installed. This would make PowerShell so much more convenient, and I believe it would help drive more users to write reusable modules and avoid duplicating modules that have already been written (likely better) by others.</p>

<p>In order for this to work though, it has to be baked directly into the PowerShell architecture by the PowerShell team; it’s not something that a 3rd party could do. So to try and bring this feature request to Microsoft’s attention, I have create <a href="https://connect.microsoft.com/PowerShell/feedback/details/800050/centralized-package-management-for-powershell">a Suggestion on the MS Connect site. Please go up-vote it</a>.</p>

<p>Before thinking to create a feature request for this (duh), I spammed some of my favourite PowerShell Twitter accounts (@<a href="http://twitter.com/JamesBru">JamesBru</a> @<a href="http://twitter.com/ShayLevy">ShayLevy</a> @<a href="http://twitter.com/dfinke">dfinke</a> @<a href="http://twitter.com/PowerShellMag">PowerShellMag</a> @<a href="http://twitter.com/StevenMurawski">StevenMurawski</a> @<a href="http://twitter.com/JeffHicks">JeffHicks</a> @<a href="http://twitter.com/ScriptingGuys">ScriptingGuys</a>) to bring it to their attention and get their thoughts; sorry about that guys! This blog’s comments are a better forum than Twitter for discussing these types of things.</p>

<p>If you have thoughts on centralized package management for PowerShell, or have a better solution for dealing with distributing scripts that depend on modules, please leave a comment below. Thanks.</p>

<p>Happy coding!</p>

<h2 id="update">Update</h2>

<p>While PowerShell does not provide a native module management solution, Joel “Jaykul” Bennett <a href="http://poshcode.org/PoshCode.psm1">has written one</a> and all of the modules are hosted at <a href="http://poshcode.org/">http://poshcode.org/</a>, although I believe it can download modules from other sources as well (e.g. GitHub or any other URL). One place that it cannot download files from is CodePlex since CodePlex does not provide direct download links to the latest versions of files or to their download links (it is done through Javascript). Please go up-vote <a href="https://codeplex.codeplex.com/workitem/26859">this issue</a> and <a href="https://codeplex.codeplex.com/workitem/25828">this issue</a> to try and get this restriction removed.</p>

<h2 id="update-2">Update 2</h2>

<p>PowerShell does now provide a native module management solution using the <code class="language-plaintext highlighter-rouge">PowerShellGet</code> module with modules hosted on <a href="https://www.powershellgallery.com/">the PowerShell Gallery</a> :-)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Automatic" /><category term="Centralize" /><category term="Centralized Package Management" /><category term="Download" /><category term="Package" /><category term="PowerShell" /><summary type="html"><![CDATA[TL;DR - PowerShell needs centralized package management. Please go up-vote this request to have it added to PowerShell.]]></summary></entry><entry><title type="html">Getting Custom TFS Checkin Policies To Work When Committing From The Command Line (i.e. tf checkin)</title><link href="https://blog.danskingdom.com/getting-custom-tfs-checkin-policies-to-work-when-committing-from-the-command-line-i-e-tf-checkin/" rel="alternate" type="text/html" title="Getting Custom TFS Checkin Policies To Work When Committing From The Command Line (i.e. tf checkin)" /><published>2013-09-06T21:28:08+00:00</published><updated>2013-09-06T21:28:08+00:00</updated><id>https://blog.danskingdom.com/getting-custom-tfs-checkin-policies-to-work-when-committing-from-the-command-line-i-e-tf-checkin</id><content type="html" xml:base="https://blog.danskingdom.com/getting-custom-tfs-checkin-policies-to-work-when-committing-from-the-command-line-i-e-tf-checkin/"><![CDATA[<p><strong>Update:</strong> I show how to have your checkin policies automatically update the registry keys shown in this blog post <a href="https://blog.danskingdom.com/template-solution-for-deploying-tfs-checkin-policies-to-multiple-versions-of-visual-studio-and-having-them-automatically-work-from-tf-exe-checkin-too/">on this newer blog post</a>. If you are not the person creating the checkin policies though, then you will still need to use the technique shown in this post.</p>

<p>I frequently check code into TFS from the command line, instead of from Visual Studio (VS), for a number of reasons:</p>

<ol>
  <li>I prefer the VS 2010 style of checkin window over the VS 2012 one, and the 2010 style window is still displayed when checking in from the command line.</li>
  <li>I use <a href="http://www.autohotkey.com/">AutoHotkey</a> to pop the checkin window via a keyboard shortcut, so I don’t need to have VS open to check files in (or navigate to the pending changes window within VS).
    <ul>
      <li>Aside: Just add this one line to your AutoHotkey script for this functionality. This sets the hotkey to Ctrl+Windows+C to pop the checkin window, but feel free to change it to something else.</li>
    </ul>

    <blockquote>
      <p>^#C UP::Run, tf checkin</p>
    </blockquote>
  </li>
  <li>Other programs, such as <a href="http://gittf.codeplex.com/">Git-Tf</a> and the Windows Explorer shell extension, call the TFS checkin window via the command line, so you don’t have the option to use the VS checkin pending changes window.</li>
</ol>

<h2 id="the-problem">The Problem</h2>

<p>The problem is that if you are using a VSIX package to deploy your custom checkin policies, the custom checkin policies will only work when checking code in via the VS GUI, and not when doing it via the command line. If you try and do it via the command line, the checkin window spits an “Internal error” for each custom checkin policy that you have, so your policies don’t run and you have to override them.</p>

<p><img src="/assets/Posts/2013/09/InternalErrorInCheckinPolicies.png" alt="Internal Error In Checkin Policies" /></p>

<p>P. Kelly mentions this problem <a href="http://blogs.msdn.com/b/phkelley/archive/2013/08/12/checkin-policy-multitargeting.aspx?wa=wsignin1.0">on his blog post</a>, and has some other great information around custom checkin policies in TFS.</p>

<p>The old <a href="http://visualstudiogallery.msdn.microsoft.com/c255a1e4-04ba-4f68-8f4e-cd473d6b971f">TFS 2010 Power Tools</a> had <a href="http://www.codewrecks.com/blog/index.php/2010/12/04/distributing-visual-studio-addin-for-the-team/">a feature for automatically distributing the checkin policies to your team</a>, but unfortunately this feature was removed from the <a href="http://visualstudiogallery.msdn.microsoft.com/b1ef7eb2-e084-4cb8-9bc7-06c3bad9148f">TFS 2012 Power Tools</a>. Instead, the Microsoft recommended way to distribute your custom checkin policies is now <a href="http://msdn.microsoft.com/en-us/library/ff363239.aspx">through a VSIX package</a>, which is nice because it can use the Extension And Updates functionality built into VS and automatically notify users of updates (without requiring users to install the TFS Power Tools). The problem is that VSIX packages are sandboxed and are not able to update the necessary registry key to make custom checkin policies work from the command line. I originally posted <a href="http://social.msdn.microsoft.com/Forums/vstudio/en-US/611a113b-b144-4ccd-8e9b-ca41306d23e2/custom-checkin-policies-do-not-work-when-using-tfexe-checkin-from-command-line">this question on the MSDN forums</a>, then I logged <a href="https://connect.microsoft.com/VisualStudio/feedback/details/788787/visual-studio-2012-custom-checkin-policies-do-not-work-when-using-tf-exe-checkin-from-command-line">a bug about this on the Connect site</a>, but MS closed it as “By Design” :-(. Maybe if it gets enough up-votes though they will re-open it (so please go up-vote it).</p>

<h2 id="the-workaround">The Workaround</h2>

<p>The good news though is that there is a work around. You simply need to copy your custom checkin policy entry from the key:</p>

<blockquote>
  <p>“HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies”</p>
</blockquote>

<p>to:</p>

<blockquote>
  <p>“HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\11.0\TeamFoundation\SourceControl\Checkin Policies” (omit the Wow6432Node on 32-bit Windows).</p>
</blockquote>

<h2 id="not-perfect-but-better">Not Perfect, but Better</h2>

<p>The bad news is that every developer (who uses the command line checkin window) will need to copy this registry value on their local machine. Furthermore, they will need to do it every time they update their checkin policies to a new version.</p>

<p>While this sucks, I’ve made it a bit better by creating a little powershell script to automate this task for you; here it is:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This script copies the required registry value so that the checkin policies will work when doing a TFS checkin from the command line.</span><span class="w">

</span><span class="c"># Turn on Strict Mode to help catch syntax-related errors.</span><span class="w">
</span><span class="c">#   This must come after a script's/function's param section.</span><span class="w">
</span><span class="c">#   Forces a function to be the first non-comment code to appear in a PowerShell Module.</span><span class="w">
</span><span class="n">Set-StrictMode</span><span class="w"> </span><span class="nt">-Version</span><span class="w"> </span><span class="nx">Latest</span><span class="w">

</span><span class="nv">$ScriptBlock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="c"># The name of the Custom Checkin Policy Entry in the Registry Key.</span><span class="w">
    </span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'YourCustomCheckinPolicyEntryNameGoesHere'</span><span class="w">

    </span><span class="c"># Get the Registry Key Entry that holds the path to the Custom Checkin Policy Assembly.</span><span class="w">
    </span><span class="nv">$CustomCheckinPolicyRegistryEntry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s1">'HKCU:\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="w">
    </span><span class="nv">$CustomCheckinPolicyEntryValue</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyRegistryEntry</span><span class="o">.</span><span class="p">(</span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="p">)</span><span class="w">

    </span><span class="c"># Create a new Registry Key Entry for the iQ Checkin Policy Assembly so they will work from the command line (as well as from Visual Studio).</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">Environment</span><span class="p">]::</span><span class="n">Is64BitOperatingSystem</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nv">$HKLMKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'HKLM:\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\11.0\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w"> </span><span class="nv">$HKLMKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'HKLM:\SOFTWARE\Microsoft\VisualStudio\11.0\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="n">Set-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$HKLMKey</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyEntryName</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$CustomCheckinPolicyEntryValue</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Run the script block as admin so it has permissions to modify the registry.</span><span class="w">
</span><span class="n">Start-Process</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nx">PowerShell</span><span class="w"> </span><span class="nt">-Verb</span><span class="w"> </span><span class="nx">RunAs</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="s2">"-Command </span><span class="nv">$ScriptBlock</span><span class="s2">"</span><span class="w">
</span></code></pre></div></div>

<p><a href="/assets/Posts/2013/09/UpdateCheckinPolicyInRegistry.zip">Download The Scripts</a></p>

<p>Note that you will need to update the script to change <strong>YourCustomCheckinPolicyEntryNameGoesHere</strong> to your specific entry’s name. Also, the “[Environment]::Is64BitOperatingSystem” check requires PowerShell V3; if you have lower than PS V3 <a href="http://social.technet.microsoft.com/Forums/windowsserver/en-US/5dfeb3ab-6265-40cd-a4ac-05428b9db5c3/determine-32-or-64bit-os">there are other ways to check if it is a 64-bit machine or not</a>.</p>

<p>If you have developers that aren’t familiar with how to run a PowerShell script, then you can include the following batch script (.cmd/.bat file extension) in the same directory as the PowerShell script, and they can run this instead by simply double-clicking it to call the PowerShell script:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SET <span class="nv">ThisScriptsDirectory</span><span class="o">=</span>%~dp0
SET <span class="nv">PowerShellScriptPath</span><span class="o">=</span>%ThisScriptsDirectory%UpdateCheckinPolicyInRegistry.ps1

:: Run the powershell script to copy the registry key into other areas of the registry so that the custom checkin policies will work when checking <span class="k">in </span>from the <span class="nb">command </span>line.
PowerShell <span class="nt">-NoProfile</span> <span class="nt">-ExecutionPolicy</span> Bypass <span class="nt">-Command</span> <span class="s2">"&amp; '%PowerShellScriptPath%'"</span>
</code></pre></div></div>

<p>Note that this batch script assumes you named the PowerShell script “UpdateCheckinPolicyInRegistry.ps1”, so if you use a different file name be sure to update it here too.</p>

<p>Your developers will still need to run this script every time after they update their checkin policies, but it’s easier and less error prone than manually editing the registry. If they want to take it a step further they could even setup a Scheduled Task to run the script once a day or something, or even implement it as a Group Policy so it automatically happens for everyone, depending on how often your company updates their checkin policies and how many developers you have.</p>

<p>Ideally I would like to simply be able to run this script during/after the VSIX installer. I have posted <a href="http://stackoverflow.com/questions/18647866/run-script-during-after-vsix-install">a question on Stack Overflow</a> to see if this is possible, but from everything I’ve read so far it doesn’t look like it; maybe in the next generation of VSIX though. If you have any other ideas on how to automate this, I would love to hear them.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="TFS" /><category term="Checkin" /><category term="Checkin Policies" /><category term="command line" /><category term="Custom" /><category term="Git-Tf" /><category term="Internal error" /><category term="PowerShell" /><category term="TF" /><category term="TFS" /><summary type="html"><![CDATA[Update: I show how to have your checkin policies automatically update the registry keys shown in this blog post on this newer blog post. If you are not the person creating the checkin policies though, then you will still need to use the technique shown in this post.]]></summary></entry><entry><title type="html">Accessing PowerShell Properties and Variables with Periods (and other special characters) in their Name</title><link href="https://blog.danskingdom.com/accessing-powershell-variables-with-periods-in-their-name/" rel="alternate" type="text/html" title="Accessing PowerShell Properties and Variables with Periods (and other special characters) in their Name" /><published>2013-09-05T19:09:32+00:00</published><updated>2013-09-05T19:09:32+00:00</updated><id>https://blog.danskingdom.com/accessing-powershell-variables-with-periods-in-their-name</id><content type="html" xml:base="https://blog.danskingdom.com/accessing-powershell-variables-with-periods-in-their-name/"><![CDATA[<h2 id="tldr">TL;DR</h2>

<p>If your PowerShell variable name contains special characters, wrap it in curly braces to get/set its value. If your PowerShell property name contains special characters, wrap it in double quotes:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Variable name with special characters</span><span class="w">
</span><span class="nv">$VariableName</span><span class="o">.</span><span class="nf">That</span><span class="o">.</span><span class="nf">Contains</span><span class="o">.</span><span class="nf">Periods</span><span class="w">     </span><span class="c"># This will NOT work.</span><span class="w">
</span><span class="nv">${VariableName.That.Contains.Periods}</span><span class="w">   </span><span class="c"># This will work.</span><span class="w">

</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">ProgramFiles</span><span class="p">(</span><span class="n">x86</span><span class="p">)</span><span class="w">      </span><span class="c"># This will NOT work, because parentheses are special characters.</span><span class="w">
</span><span class="nv">${env:ProgramFiles(x86)}</span><span class="w">    </span><span class="c"># This will work.</span><span class="w">

</span><span class="c"># Property name with special characters</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="nf">APropertyName</span><span class="o">.</span><span class="nf">That</span><span class="o">.</span><span class="nf">ContainsPeriods</span><span class="w">      </span><span class="c"># This will NOT work.</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="p">{</span><span class="n">APropertyName.That.ContainsPeriods</span><span class="p">}</span><span class="w">    </span><span class="c"># This will work.</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="s1">'APropertyName.That.ContainsPeriods'</span><span class="w">    </span><span class="c"># This will also work.</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="s2">"APropertyName.That.ContainsPeriods"</span><span class="w">    </span><span class="c"># This will work too.</span><span class="w">

</span><span class="c"># Property name with special characters stored in a variable</span><span class="w">
</span><span class="nv">$APropertyNameWithSpecialCharacters</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'APropertyName.That.ContainsPeriods'</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="nv">$APropertyNameWithSpecialCharacters</span><span class="w">     </span><span class="c"># This will NOT work.</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="p">{</span><span class="nv">$APropertyNameWithSpecialCharacters</span><span class="p">}</span><span class="w">   </span><span class="c"># This will NOT work.</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="p">(</span><span class="nv">$APropertynameWithSpecialCharacters</span><span class="p">)</span><span class="w">   </span><span class="c"># This will work.</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="s2">"</span><span class="nv">$APropertynameWithSpecialCharacters</span><span class="s2">"</span><span class="w">   </span><span class="c"># This will also work.</span><span class="w">
</span><span class="nv">$SomeObject</span><span class="o">.</span><span class="s1">'$APropertynameWithSpecialCharacters'</span><span class="w">   </span><span class="c"># This will NOT work.</span><span class="w">
</span></code></pre></div></div>

<h2 id="more-information">More Information</h2>

<p>I was recently working on a powershell script to get the values of some entries in the registry. This is simple enough:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s1">'HKCU:\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s1">'TF.iQmetrix.CheckinPolicies'</span><span class="w">
</span></code></pre></div></div>

<p>If we run this command, this is what we get back:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TF.iQmetrix.CheckinPolicies</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nx">C:\Users\Dan</span><span class="w"> </span><span class="nx">Schroeder\AppData\Local\Microsoft\VisualStudio\11.0\Extensions\mwlu1noz.4t5\TF.iQmetrix.CheckinPolicies.dll</span><span class="w">
</span><span class="n">PSPath</span><span class="w">                      </span><span class="p">:</span><span class="w"> </span><span class="nx">Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin</span><span class="w"> </span><span class="nx">Policies</span><span class="w">
</span><span class="n">PSParentPath</span><span class="w">                </span><span class="p">:</span><span class="w"> </span><span class="nx">Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl</span><span class="w">
</span><span class="n">PSChildName</span><span class="w">                 </span><span class="p">:</span><span class="w"> </span><span class="nx">Checkin</span><span class="w"> </span><span class="nx">Policies</span><span class="w">
</span><span class="n">PSDrive</span><span class="w">                     </span><span class="p">:</span><span class="w"> </span><span class="nx">HKCU</span><span class="w">
</span><span class="n">PSProvider</span><span class="w">                  </span><span class="p">:</span><span class="w"> </span><span class="nx">Microsoft.PowerShell.Core\Registry</span><span class="w">
</span></code></pre></div></div>

<p>So the actual value I’m after is stored in the “TF.iQmetrix.CheckinPolicies” property of the object returned by <code class="language-plaintext highlighter-rouge">Get-ItemProperty</code>; notice that this property name has periods in it. So let’s store this object in a variable to make it easier to access it’s properties, and do a quick <code class="language-plaintext highlighter-rouge">Get-Member</code> on it just to show some more details:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$RegistryEntry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s1">'HKCU:\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s1">'TF.iQmetrix.CheckinPolicies'</span><span class="w">
</span><span class="nv">$RegistryEntry</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Get-Member</span><span class="w">
</span></code></pre></div></div>

<p>And this is what <code class="language-plaintext highlighter-rouge">Get-Member</code> shows us:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">   </span><span class="n">TypeName:</span><span class="w"> </span><span class="nx">System.Management.Automation.PSCustomObject</span><span class="w">

</span><span class="n">Name</span><span class="w">                        </span><span class="nx">MemberType</span><span class="w">   </span><span class="nx">Definition</span><span class="w">
</span><span class="o">----</span><span class="w">                        </span><span class="o">----------</span><span class="w">   </span><span class="o">----------</span><span class="w">
</span><span class="n">Equals</span><span class="w">                      </span><span class="nx">Method</span><span class="w">       </span><span class="nx">bool</span><span class="w"> </span><span class="nx">Equals</span><span class="p">(</span><span class="n">System.Object</span><span class="w"> </span><span class="nx">obj</span><span class="p">)</span><span class="w">
</span><span class="n">GetHashCode</span><span class="w">                 </span><span class="nx">Method</span><span class="w">       </span><span class="nx">int</span><span class="w"> </span><span class="nx">GetHashCode</span><span class="p">()</span><span class="w">
</span><span class="n">GetType</span><span class="w">                     </span><span class="nx">Method</span><span class="w">       </span><span class="nx">type</span><span class="w"> </span><span class="nx">GetType</span><span class="p">()</span><span class="w">
</span><span class="n">ToString</span><span class="w">                    </span><span class="nx">Method</span><span class="w">       </span><span class="nx">string</span><span class="w"> </span><span class="nx">ToString</span><span class="p">()</span><span class="w">
</span><span class="n">PSChildName</span><span class="w">                 </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="nx">System.String</span><span class="w"> </span><span class="nx">PSChildName</span><span class="o">=</span><span class="n">Checkin</span><span class="w"> </span><span class="nx">Policies</span><span class="w">
</span><span class="n">PSDrive</span><span class="w">                     </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="nx">System.Management.Automation.PSDriveInfo</span><span class="w"> </span><span class="nx">PSDrive</span><span class="o">=</span><span class="n">HKCU</span><span class="w">
</span><span class="nx">PSParentPath</span><span class="w">                </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="nx">System.String</span><span class="w"> </span><span class="nx">PSParentPath</span><span class="o">=</span><span class="n">Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl</span><span class="w">
</span><span class="nx">PSPath</span><span class="w">                      </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="nx">System.String</span><span class="w"> </span><span class="nx">PSPath</span><span class="o">=</span><span class="n">Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin</span><span class="w"> </span><span class="nx">Policies</span><span class="w">
</span><span class="n">PSProvider</span><span class="w">                  </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="nx">System.Management.Automation.ProviderInfo</span><span class="w"> </span><span class="nx">PSProvider</span><span class="o">=</span><span class="n">Microsoft.PowerShell.Core\Registry</span><span class="w">
</span><span class="nx">TF.iQmetrix.CheckinPolicies</span><span class="w"> </span><span class="nx">NoteProperty</span><span class="w"> </span><span class="nx">System.String</span><span class="w"> </span><span class="nx">TF.iQmetrix.CheckinPolicies</span><span class="o">=</span><span class="n">C:\Users\Dan</span><span class="w"> </span><span class="nx">Schroeder\AppData\Local\Microsoft\VisualStudio\11.0\Extensions\mwlu1noz.4t5\TF.iQmetrix.CheckinPolicies.dll</span><span class="w">
</span></code></pre></div></div>

<p>So in PowerShell ISE I type <code class="language-plaintext highlighter-rouge">$RegistryEntry.</code> and intellisense pops up showing me that TF.iQmetrix.CheckinPolicies is indeed a property on this object that I can access.</p>

<p><img src="/assets/Posts/2013/09/PowerShell-ISE-Intellisense.png" alt="PowerShell ISE Intellisense" /></p>

<p>So I try and display the value of that property to the console using:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$RegistryEntry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s1">'HKCU:\Software\Microsoft\VisualStudio\11.0_Config\TeamFoundation\SourceControl\Checkin Policies'</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s1">'TF.iQmetrix.CheckinPolicies'</span><span class="w">
</span><span class="nv">$RegistryEntry</span><span class="o">.</span><span class="nf">TF</span><span class="o">.</span><span class="nf">iQmetrix</span><span class="o">.</span><span class="nf">CheckinPolicies</span><span class="w">
</span></code></pre></div></div>

<p>But nothing is displayed :-(</p>

<p>While PowerShell ISE does color-code the line <code class="language-plaintext highlighter-rouge">$RegistryEntry.TF.iQmetrix.CheckinPolicies</code> to have the object color different than the property color, if you just look at it in plain text, something clearly looks off about it. How does PowerShell know that the property name is “TF.iQmetrix.CheckinPolicies”, and not that “TF” is a property with an “iQmetrix” property on it, with a “CheckinPolicies” property on that. Well, it doesn’t.</p>

<p>I did some Googling and looked on StackOverflow, but couldn’t a solution to this problem. I found slightly related <a href="http://stackoverflow.com/questions/9984065/cannot-resolve-environment-variables-in-powershell-with-periods-in-them">posts involving environmental variables with periods in their name</a>, but that solution did not work in this case. So after some random trial-and-error I stumbled onto the solution. You have to wrap the property name in curly braces:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$RegistryEntry</span><span class="o">.</span><span class="nf">TF</span><span class="o">.</span><span class="nf">iQmetrix</span><span class="o">.</span><span class="nf">CheckinPolicies</span><span class="w">      </span><span class="c"># This is WRONG. Nothing will be returned.</span><span class="w">
</span><span class="nv">$RegistryEntry</span><span class="o">.</span><span class="p">{</span><span class="n">TF.iQmetrix.CheckinPolicies</span><span class="p">}</span><span class="w">    </span><span class="c"># This is RIGHT. The property's value will returned.</span><span class="w">
</span></code></pre></div></div>

<p>I later refactored my script to store the “TF.iQmetrix.CheckinPolicies” name in a variable and found that I couldn’t use the curly braces anymore. After more trial-and-error I discovered that using parentheses instead works:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$EntryName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'TF.iQmetrix.CheckinPolicies'</span><span class="w">

</span><span class="nv">$RegistryEntry</span><span class="o">.</span><span class="nv">$EntryName</span><span class="w">       </span><span class="c"># This is WRONG. Nothing will be returned.</span><span class="w">
</span><span class="nv">$RegistryEntry</span><span class="o">.</span><span class="p">{</span><span class="nv">$EntryName</span><span class="p">}</span><span class="w">     </span><span class="c"># This is WRONG. Nothing will be returned.</span><span class="w">
</span><span class="nv">$RegistryEntry</span><span class="o">.</span><span class="p">(</span><span class="nv">$EntryName</span><span class="p">)</span><span class="w">     </span><span class="c"># This is RIGHT. The property's value will be returned.</span><span class="w">
</span><span class="nv">$RegistryEntry</span><span class="o">.</span><span class="s2">"</span><span class="nv">$EntryName</span><span class="s2">"</span><span class="w">     </span><span class="c"># This is RIGHT too. The property's value will be returned.</span><span class="w">
</span></code></pre></div></div>

<p>So there you have it. If for some reason you have a variable or property name that contains periods, wrap it in curly braces, or parenthesis if you are storing it in a variable.</p>

<p>Hopefully this makes it’s way to the top of the Google search results so you don’t waste as much time on it as I did.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Dot" /><category term="Name" /><category term="Period" /><category term="PowerShell" /><category term="Property" /><category term="Registry" /><category term="Special Character" /><category term="Variable" /><summary type="html"><![CDATA[TL;DR]]></summary></entry><entry><title type="html">Add ability to add tabs to the end of a line in Windows PowerShell ISE</title><link href="https://blog.danskingdom.com/add-ability-to-add-tabs-to-the-end-of-a-line-in-windows-powershell-ise/" rel="alternate" type="text/html" title="Add ability to add tabs to the end of a line in Windows PowerShell ISE" /><published>2013-06-24T22:06:52+00:00</published><updated>2013-06-24T22:06:52+00:00</updated><id>https://blog.danskingdom.com/add-ability-to-add-tabs-to-the-end-of-a-line-in-windows-powershell-ise</id><content type="html" xml:base="https://blog.danskingdom.com/add-ability-to-add-tabs-to-the-end-of-a-line-in-windows-powershell-ise/"><![CDATA[<p>In the preamble of <a href="https://blog.danskingdom.com/powershell-ise-multiline-comment-and-uncomment-done-right-and-other-ise-gui-must-haves/">an earlier post</a> I mentioned that one of the little things that bugs me about Windows PowerShell ISE is that you can add tabs to the start of a line, but not to the end of a line. This is likely because it would interfere with the tab-completion feature. I still like to be able to put tabs on the end of my code lines though so that I can easily line up my comments, like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$processes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Process</span><span class="w"> </span><span class="c"># Get all of the processes.</span><span class="w">
</span><span class="nv">$myProcesses</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$processes</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where</span><span class="w"> </span><span class="p">{</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Company</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"MyCompany"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c"># Get my company's processes.</span><span class="w">
</span></code></pre></div></div>

<p>We can add the functionality to allow us to insert a tab at the end of a line, but it involves modifying the PowerShell <strong>ISE</strong> profile, so opening that file for editing is the first step.</p>

<p>To edit your PowerShell ISE profile:</p>

<ol>
  <li>Open <strong>Windows PowerShell ISE</strong> (not Windows PowerShell, as we want to edit the ISE profile instead of the regular PowerShell profile).</li>
  <li>In the Command window type: <code class="language-plaintext highlighter-rouge">psedit $profile</code>
    <ul>
      <li>If you get an error that it cannot find the path, then first type the following to create the file before trying #2 again: <code class="language-plaintext highlighter-rouge">New-Item $profile -ItemType File -Force</code></li>
    </ul>
  </li>
</ol>

<p>And now that you have your PowerShell ISE profile file open for editing, you can append the following code to it:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Add a new option in the Add-ons menu to insert a tab.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Insert Tab"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Insert Tab"</span><span class="p">,{</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">InsertText</span><span class="p">(</span><span class="s2">"</span><span class="se">`t</span><span class="s2">"</span><span class="p">)},</span><span class="s2">"Ctrl+Shift+T"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This will allow you to use <strong>Ctrl+Shift+T</strong> to insert a tab anywhere in the editor, including at the end of a line. I wanted to use Shift+Tab, but apparently that shortcut is already used by the editor somewhere, even though it doesn’t seem to do anything when I press it. Feel free to change the keyboard shortcut to something else if you like.</p>

<p>I hope this helps make your PowerShell ISE experience a little better.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Editor" /><category term="ISE" /><category term="PowerShell" /><category term="PowerShell ISE" /><category term="Profile" /><category term="Tab" /><category term="Windows PowerShell" /><category term="Windows PowerShell ISE" /><summary type="html"><![CDATA[In the preamble of an earlier post I mentioned that one of the little things that bugs me about Windows PowerShell ISE is that you can add tabs to the start of a line, but not to the end of a line. This is likely because it would interfere with the tab-completion feature. I still like to be able to put tabs on the end of my code lines though so that I can easily line up my comments, like this:]]></summary></entry><entry><title type="html">Automatically Create Your Project’s NuGet Package Every Time It Builds, Via NuGet</title><link href="https://blog.danskingdom.com/automatically-create-your-projects-nuget-package-every-time-it-builds-via-nuget/" rel="alternate" type="text/html" title="Automatically Create Your Project’s NuGet Package Every Time It Builds, Via NuGet" /><published>2013-06-22T16:16:37+00:00</published><updated>2013-06-22T16:16:37+00:00</updated><id>https://blog.danskingdom.com/automatically-create-your-projects-nuget-package-every-time-it-builds-via-nuget</id><content type="html" xml:base="https://blog.danskingdom.com/automatically-create-your-projects-nuget-package-every-time-it-builds-via-nuget/"><![CDATA[<p>So you’ve got a super awesome library/assembly that you want to share with others, but you’re too lazy to actually use NuGet to package it up and upload it to the gallery; or maybe you don’t know how to create a NuGet package and don’t have the time or desire to learn. Well, my friends, now this can all be handled for you automatically.</p>

<p>A couple weeks ago <a href="https://blog.danskingdom.com/create-and-publish-your-nuget-package-in-one-click-with-the-new-nugetpackage-powershell-script/">I posted about a new PowerShell script that I wrote</a> and put up on CodePlex, called <a href="https://newnugetpackage.codeplex.com/">New-NuGetPackage PowerShell Script</a>, to make creating new NuGet packages quick and easy. Well, I’ve taken that script one step further and use it in a new NuGet package called <a href="https://nuget.org/packages/CreateNewNuGetPackageFromProjectAfterEachBuild/">Create New NuGet Package From Project After Each Build</a> (real creative name, right) that you can add to your Visual Studio projects. The NuGet package will, you guessed it, pack your project and its dependencies up into a NuGet package (i.e. .nupkg file) and place it in your project’s output directory beside the generated dll/exe file. Now creating your own NuGet package is as easy as adding a NuGet package to your project, which if you’ve never done before is dirt simple.</p>

<p>I show how to add the NuGet package to your Visual Studio project in <a href="https://newnugetpackage.codeplex.com/wikipage?title=NuGet%20Package%20To%20Create%20A%20NuGet%20Package%20From%20Your%20Project%20After%20Every%20Build">the New-NuGetPackage PowerShell Script documentation</a> (hint: search for <strong>“New NuGet Package”</strong> (include quotes) to find it in the VS NuGet Package Manager search results), as well as how you can push your package to the NuGet Gallery in just a few clicks.</p>

<p>Here’s a couple screenshots from the documentation on installing the NuGet Package:</p>

<p><img src="/assets/Posts/2013/06/NavigateToManageNugetPackages.png" alt="Navigate To Manage NuGet Packages" /> <img src="/assets/Posts/2013/06/InstallNuGetPackageFromPackageManager.png" alt="Install NuGet Package From Package Manager" /></p>

<p>Here you can see the new PostBuildScripts folder it adds to your project, and that when you build your project, a new .nupkg file is created in the project’s Output directory alongside the dll/exe.</p>

<p><img src="/assets/Posts/2013/06/FilesAddedToProject.png" alt="Files Added To Project" /> <img src="/assets/Posts/2013/06/NuGetPackageInOutputDirectory.png" alt="NuGet Package In Output Directory&quot;" /></p>

<p>So now that packaging your project up in a NuGet package can be fully automated with about 30 seconds of effort, and you can push it to the <a href="https://nuget.org/">NuGet Gallery</a> in a few clicks, there is no reason for you to not share all of the awesome libraries you write.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="NuGet" /><category term="PowerShell" /><category term="Visual Studio" /><category term="After" /><category term="Automatic" /><category term="Automatically" /><category term="Build" /><category term="Create" /><category term="New" /><category term="NuGet" /><category term="pack" /><category term="Package" /><category term="project" /><category term="push" /><category term="Visual Studio" /><summary type="html"><![CDATA[So you’ve got a super awesome library/assembly that you want to share with others, but you’re too lazy to actually use NuGet to package it up and upload it to the gallery; or maybe you don’t know how to create a NuGet package and don’t have the time or desire to learn. Well, my friends, now this can all be handled for you automatically.]]></summary></entry><entry><title type="html">PowerShell ISE: Multi-line Comment and Uncomment Done Right, and other ISE GUI must haves</title><link href="https://blog.danskingdom.com/powershell-ise-multiline-comment-and-uncomment-done-right-and-other-ise-gui-must-haves/" rel="alternate" type="text/html" title="PowerShell ISE: Multi-line Comment and Uncomment Done Right, and other ISE GUI must haves" /><published>2013-06-19T23:42:07+00:00</published><updated>2013-06-19T23:42:07+00:00</updated><id>https://blog.danskingdom.com/powershell-ise-multiline-comment-and-uncomment-done-right-and-other-ise-gui-must-haves</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-ise-multiline-comment-and-uncomment-done-right-and-other-ise-gui-must-haves/"><![CDATA[<p>I’ve written some code that you can add to your ISE profile that adds keyboard shortcuts to quickly comment and uncomment lines in PowerShell ISE. So you can quickly turn this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">This</span><span class="w"> </span><span class="nx">is</span><span class="w"> </span><span class="nx">some</span><span class="w">
    </span><span class="n">code</span><span class="w"> </span><span class="nx">and</span><span class="w"> </span><span class="nx">here</span><span class="w"> </span><span class="nx">is</span><span class="w">
</span><span class="n">some</span><span class="w"> </span><span class="nx">more</span><span class="w"> </span><span class="nx">code.</span><span class="w">
</span></code></pre></div></div>

<p>into this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#This is some</span><span class="w">
</span><span class="c">#   code and here is</span><span class="w">
</span><span class="c">#some more code.</span><span class="w">
</span></code></pre></div></div>

<p>and back again.</p>

<p>Feel free to skip the Preamble and get right to the good stuff.</p>

<h2 id="preamble">Preamble</h2>

<p>I’ve only been writing PowerShell (PS) for about 6 months now, and have a love-hate relationship with it. It is simply a wonderful tool…once you understand how it works and have learnt some of the nuances. I’ve gotten hung up for hours on end with things that should be simple, but aren’t. For example, if you have an array of strings, but the array actually only contains a single string, when you go to iterate over the array instead of giving you the string it will iterator over the characters in the string….but if you have multiple strings in your array then everything works fine (btw the trick is you have to explicitly cast your array to a string array when iterating over it). This is only one small example, but I’ve found I’ve hit many little Gotcha’s like this since I started with PS. So PS is a great tool, but has a deceptively steep learning curve in my opinion; it’s easy to get started with it, especially if you have a .Net background, but there are many small roadblocks that just shouldn’t be there. Luckily, we have <a href="http://stackoverflow.com/">Stack Overflow</a> :-)</p>

<p>Anyways, as a PS newb one of the first things I did was go look for a nice editor to work in; <strong>intellisense was a must</strong>. First I tried PowerShell ISE v3 since it comes with Windows out of the box, but was quickly turned off at how featureless the GUI is. Here’s a quick list of lacking UI components that immediately turned me off of ISE’s Script Pane:</p>

<ol>
  <li>No keyboard shortcut to quickly comment/uncomment code (<a href="https://connect.microsoft.com/PowerShell/feedback/details/711231/ise-v3-need-to-be-able-to-comment-a-series-of-lines-in-a-block-of-code">go up-vote to get this added</a>).</li>
  <li>No “Save All Files” keyboard shortcut (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790577/windows-powershell-ise-implement-a-save-all-files-feature-and-tie-it-to-ctrl-shift-s">go up-vote to get this added</a>).</li>
  <li>No ability to automatically reopen files that were open when I closed ISE; there’s the Recent Documents menu, but that’s an extra 10 clicks every time I open ISE (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790578/windows-powershell-ise-add-ability-to-save-load-session-state">go up-vote to get this added</a>).</li>
  <li>Can not split the tab windows to show two files side by side (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790581/windows-powershell-ise-add-ability-to-show-multiple-editors-side-by-side">go up-vote to get this added</a>).</li>
  <li>Can not drag a tab out of ISE to show it on another monitor (<a href="https://connect.microsoft.com/PowerShell/feedback/details/698161/powershell-ise-pane-breakout-for-multi-monitor-use">go up-vote to get this added</a>).</li>
  <li>Can not enter tabs on the end of lines; I do this all of the time to line up my comments placed on the end of the code line. I’m guessing this is “by design” though to allow the tab-completion to work (I show a workaround for this <a href="https://blog.danskingdom.com/add-ability-to-add-tabs-to-the-end-of-a-line-in-windows-powershell-ise/">in this post</a>).</li>
  <li>Find/Replace window does not have an option to wrap around the end of the file; it will only search down or up depending on if the Search Up checkbox is checked (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790583/windows-powershell-ise-add-wrap-around-option-to-find-replace-dialogues">go up-vote to get this added</a>).</li>
  <li>Can’t simply use Ctrl+F3 to search for the current/selected word/text; you have to use the actual Find window (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790584/windows-powershell-ise-add-keyboard-shortcuts-for-finding-text-in-a-file">go up-vote to get this added</a>).</li>
  <li>When you perform an undo/redo, the caret and view don’t jump to the text being undone/redone, so if the text being changed is outside of the viewable area you can’t see what is being changed (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790586/windows-powershell-ise-caret-and-view-do-not-jump-to-text-being-undone-redone">up-vote to get this fixed</a>).</li>
  <li>Can not re-arrange tabs; you have to close and reopen them if you want to change their order (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790585/windows-powershell-ise-add-ability-to-rearrange-tabs">go up-vote to get this added</a>).</li>
  <li>The intellisense sometimes becomes intermittent or stops all together and you have to restart ISE (<a href="https://connect.microsoft.com/PowerShell/feedback/details/772736/powershell-ise-v3-rtm-intellisense-does-not-show-up-consistently">go up-vote to get this fixed</a>).</li>
  <li>Double-clicking a cmdlet or variable name does not select the entire cmdlet/variable name; e.g. doesn’t fully select “Get-Help” or “$variable” (<a href="https://connect.microsoft.com/PowerShell/feedback/details/790661/windows-powershell-ise-double-click-should-select-entire-cmdlet-variable-name">go up-vote to get this added</a>).</li>
</ol>

<p>It took me all of 5 minutes to say “ISE is not a mature enough editor for me”; I guess I’ve been spoiled by working in Visual Studio for so many years. So I went and found <a href="http://powergui.org/index.jspa">PowerGUI</a>, which was pretty good and I liked it quite a bit at first. It’s been a while since I’ve used it so honestly I can’t remember all of the reasons why I decided to switch away from it. I remember one problem of having to constantly start a new PS session in order to pick up changes to functions that I made (I think they had a button for that at least), as well as intellisense not being reliable, and having problems with debugging. Anyways, I decided to switch to <a href="http://www.idera.com/productssolutions/freetools/powershellplus">PowerShellPlus</a> and was much happier with it. It still wasn’t perfect; I still had problems with intellisense and debugging, but I was still happy. I especially liked that I could search for and download other people’s script easily from it, which is great for learning. As I kept using it though, it kept taking longer and longer to load. After about 3 months I found myself waiting about a minute for it to open, and then once it was open, another 15 seconds or so to open all of my previously open tabs; and I have an SSD. So I thought I would give ISE another shot, mainly because it is already installed by default and I now know that I can customize it somewhat with the add-ons.</p>

<h2 id="other-must-have-ise-gui-add-ons">Other Must Have ISE GUI Add-ons</h2>

<p>After looking for not too long, I found posts on <a href="http://blogs.msdn.com/b/powershell/">the PowerShell Team’s blog</a> which address the <a href="http://blogs.msdn.com/b/powershell/archive/2010/06/05/save-all-powershell-ise-files-for-thor-s-sake.aspx">Save All</a> and <a href="http://blogs.msdn.com/b/powershell/archive/2010/06/05/export-and-import-powershell-ise-opened-files.aspx">Save/Restore ISE State</a> issues (#2 and #3 in my list above). These are must haves, and I provide them alongside my code in the last section below.</p>

<h2 id="why-my-implementation-is-better">Why My Implementation Is Better</h2>

<h3 id="other-solutions-and-why-they-suck">Other solutions and why they suck</h3>

<p>So of course before writing my own multiline comment/uncomment code I went searching for an existing solution, and I did find two. The first one was recommended by <a href="http://blogs.technet.com/b/heyscriptingguy/">Ed Wilson (aka Hey, Scripting Guy!)</a> at the bottom of <a href="http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/19/automatically-add-comments-in-powershell-work-with-text-files-and-customize-office-communicator.aspx">this post</a>. He recommended using the <a href="http://archive.msdn.microsoft.com/PowerShellPack">PowerShellPack</a>. I downloaded it, added it to my PS profile, and gave it a try. I was instantly disappointed. The <a href="http://gallery.technet.microsoft.com/PowerShell-ISE-Add-On-to-38ec9dab">other solution I found</a> was by Clatonh (a Microsoft employee). Again, I added his code to my ISE profile to try it out, and was disappointed.</p>

<p>Here are the problems with their solutions:</p>

<ol>
  <li>If you only have part of a line selected, it places the comment character at the beginning of your selection, not at the beginning of the line (undesirable, both).</li>
  <li>If you don’t have any text selected, nothing gets commented out (undesirable, both).</li>
  <li>If you have any blank lines selected in your multiline selection, it removes them (unacceptable, PowerShellPack only).</li>
  <li>It uses block comments (i.e. &lt;# … #&gt;)! (unacceptable (block comments are the devil), Clatonh’s solution only) I’m not sure if the PowerShellPack problems are because it was written for PS v2 and I’m using v3 on Windows 8, but either way that was unacceptable for me.</li>
</ol>

<p>You might be wondering why #4 is on my list and why I hate block comments so much. Block comments themselves aren’t <em>entirely</em> a bad idea; the problem is that 99% of editors (including PS ISE) don’t handle nested block comments properly. For example, if I comment out 3 lines in a function using block comments, and then later go and comment out the entire function using block comments, I’ll get a compiler error (or in PS’s case, a run-time error); this is because the first closing “#&gt;” tag will be considered the closing tag for both the 1st and 2nd opening “&lt;#” tags; so everything between the 1st and 2nd closing “#&gt;” tag won’t actually be commented out. Because of this it is just easier to avoid block comments all together, even for that paragraph of comment text you are about to write (you do comment your code, right?).</p>

<h3 id="my-solution">My Solution</h3>

<ol>
  <li>Uses single line comments (no block comments!).</li>
  <li>Places the comment character at the beginning of the line, even if you have middle of line selected.</li>
  <li>Comments out the line that the caret is on if no text is selected.</li>
  <li>Preserves blank lines, and doesn’t comment them out.</li>
</ol>

<h2 id="show-me-the-code">Show Me The Code</h2>

<p>Before I give you the code, we are going to want to add it to your PowerShell <strong>ISE</strong> profile, so we need to open that file.</p>

<p>To edit your PowerShell ISE profile:</p>

<ol>
  <li>Open <strong>Windows PowerShell ISE</strong> (not Windows PowerShell, as we want to edit the ISE profile instead of the regular PowerShell profile).</li>
  <li>In the Command window type: **psedit $profile
 If you get an error that it cannot find the path, then first type the following to create the file before trying #2 again: <code class="language-plaintext highlighter-rouge">New-Item $profile –ItemType File –Force</code></li>
</ol>

<p>And now that you have your PowerShell ISE profile file open for editing, here’s the code to append to it in order to get the comment/uncomment commands and keyboard shortcuts (or keep reading and get ALL the code from further down). You will then need to restart PowerShell ISE for the new commands to show up and work. I’ll mention too that I’ve only tested this on Windows 8 with PowerShell v3.0.</p>

<p><a href="/assets/Posts/2013/09/CommentSelectedLines.zip">Download The Code</a></p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Define our constant variables.</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NEW_LINE_STRING</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="se">`r`n</span><span class="s2">"</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$COMMENT_STRING</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"#"</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers</span><span class="p">([</span><span class="n">bool</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Exands the selected text to make sure the entire lines are selected.
    Returns $null if we can't determine with certainty which lines to select and the

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Exands the selected text to make sure the entire lines are selected.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DoNothingWhenNotCertainOfWhichLinesToSelect
    Under the following edge case we can't determine for sure which lines in the file are selected.
    If this switch is not provided and the edge case is encountered, we will guess and attempt to select the entire selected lines, but we may guess wrong and select the lines above/below the selected lines.
    If this switch is provided and the edge case is encountered, no lines will be selected.

    Edge Case:
    - When the selected text occurs multiple times in the document, directly above or below the selected text.

    Example:
    abc
    abc
    abc

    - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get selected, so it shouldn't be a big deal.
    But if it bugs you, you can provide this switch.

    .OUTPUT
    PSObject. Returns a PSObject with the properties FirstLineNumber and LastLineNumber, which correspond to the first and last line numbers of the selected text.
#&gt;</span><span class="w">

    </span><span class="c"># Backup all of the original info before we modify it.</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">CaretLine</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$originalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$originalCaretLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">CaretLineText</span><span class="w">

    </span><span class="c"># Assume only one line is selected.</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$textToSelectFirstLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$textToSelectLastLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">

    </span><span class="c">#------------------------</span><span class="w">
    </span><span class="c"># Before we process the selected text, we need to make sure all selected lines are fully selected (i.e. the entire line is selected).</span><span class="w">
    </span><span class="c">#------------------------</span><span class="w">

    </span><span class="c"># If no text is selected, OR only part of one line is selected (and it doesn't include the start of the line), select the entire line that the caret is currently on.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="o">-le</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="o">!</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="o">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nv">$NEW_LINE_STRING</span><span class="p">))</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectCaretLine</span><span class="p">()</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="c"># Else the first part of one line (or the entire line), or multiple lines are selected.</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># Get the number of lines in the originally selected text.</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedText</span><span class="o">.</span><span class="nf">Split</span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$NEW_LINE_STRING</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringSplitOptions</span><span class="p">]::</span><span class="nx">None</span><span class="p">)</span><span class="w">
        </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="o">.</span><span class="nf">Length</span><span class="w">

        </span><span class="c"># If only one line is selected, make sure it is fully selected.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-le</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectCaretLine</span><span class="p">()</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="c"># Else there are multiple lines selected, so make sure the first character of the top line is selected (so that we put the comment character at the start of the top line, not in the middle).</span><span class="w">
        </span><span class="c"># The first character of the bottom line will always be selected when multiple lines are selected, so we don't have to worry about making sure it is selected; only the top line.</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># Determine if the caret is on the first or last line of the selected text.</span><span class="w">
            </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$firstLineOfOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lastLineOfOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="p">[</span><span class="nv">$originalSelectedTextArray</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">]</span><span class="w">

            </span><span class="c"># If the caret is definitely on the first line.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="nv">$firstLineOfOriginalSelectedText</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$lastLineOfOriginalSelectedText</span><span class="p">))</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else if the caret is definitely on the last line.</span><span class="w">
            </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$lastLineOfOriginalSelectedText</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="nv">$firstLineOfOriginalSelectedText</span><span class="p">))</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else we need to do further analysis to determine if the caret is on the first or last line of the selected text.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$numberOfLinesInFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">LineCount</span><span class="w">

                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$caretOnFirstLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">Empty</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because array starts at 0 and file lines start at 1.</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnFirstLineArrayStopIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">

                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$caretOnLastLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">Empty</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnLastLineArrayStopIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">  </span><span class="c"># -1 because array starts at 0 and file lines start at 1.</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnLastLineArrayStartIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnLastLineArrayStopIndex</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">

                </span><span class="c"># If the caret being on the first line would cause us to go "off the file", then we know the caret is on the last line.</span><span class="w">
                </span><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnFirstLineArrayStopIndex</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$numberOfLinesInFile</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
                </span><span class="p">}</span><span class="w">
                </span><span class="c"># If the caret being on the last line would cause us to go "off the file", then we know the caret is on the first line.</span><span class="w">
                </span><span class="kr">elseif</span><span class="w"> </span><span class="p">((</span><span class="nv">$caretOnLastLineArrayStartIndex</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnLastLineArrayStopIndex</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$numberOfLinesInFile</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
                </span><span class="p">}</span><span class="w">
                </span><span class="c"># Else we still don't know where the caret is.</span><span class="w">
                </span><span class="kr">else</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$filesTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Text</span><span class="o">.</span><span class="nf">Split</span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$NEW_LINE_STRING</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringSplitOptions</span><span class="p">]::</span><span class="nx">None</span><span class="p">)</span><span class="w">

                    </span><span class="c"># Get the text of the lines where the caret is on the first line of the selected text.</span><span class="w">
                    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$caretOnFirstLineTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@([</span><span class="n">string</span><span class="p">]</span><span class="err">::Empty</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="c"># Declare an array with the number of elements required.</span><span class="w">
                    </span><span class="p">[</span><span class="n">System.Array</span><span class="p">]::</span><span class="n">Copy</span><span class="p">(</span><span class="nv">$filesTextArray</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnFirstLineTextArray</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="nv">$caretOnFirstLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnFirstLineTextArray</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="nv">$NEW_LINE_STRING</span><span class="w">

                    </span><span class="c"># Get the text of the lines where the caret is on the last line of the selected text.</span><span class="w">
                    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$caretOnLastLineTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@([</span><span class="n">string</span><span class="p">]</span><span class="err">::Empty</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="c"># Declare an array with the number of elements required.</span><span class="w">
                    </span><span class="p">[</span><span class="n">System.Array</span><span class="p">]::</span><span class="n">Copy</span><span class="p">(</span><span class="nv">$filesTextArray</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnLastLineArrayStartIndex</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnLastLineTextArray</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="nv">$caretOnLastLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnLastLineTextArray</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="nv">$NEW_LINE_STRING</span><span class="w">

                    </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnFirstLineText</span><span class="o">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nv">$originalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnLastLineText</span><span class="o">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nv">$originalSelectedText</span><span class="p">)</span><span class="w">

                    </span><span class="c"># If the selected text is only within the text of when the caret is on the first line, then we know for sure the caret is on the first line.</span><span class="w">
                    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                    </span><span class="c"># Else if the selected text is only within the text of when the caret is on the last line, then we know for sure the caret is on the last line.</span><span class="w">
                    </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                    </span><span class="c"># Else if the selected text is in both sets of text, then we don't know for sure if the caret is on the first or last line.</span><span class="w">
                    </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="c"># If we shouldn't do anything since we might comment out text that is not selected by the user, just exit this function and return null.</span><span class="w">
                        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="p">)</span><span class="w">
                        </span><span class="p">{</span><span class="w">
                            </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w">
                        </span><span class="p">}</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                    </span><span class="c"># Else something went wrong and there is a flaw in this logic, since the selected text should be in one of our two strings, so let's just guess!</span><span class="w">
                    </span><span class="kr">else</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="n">Write-Error</span><span class="w"> </span><span class="s2">"WHAT HAPPENED?!?! This line should never be reached. There is a flaw in our logic!"</span><span class="w">
                        </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># Assume the caret is on the first line of the selected text, so we want to select text from the caret's line downward.</span><span class="w">
            </span><span class="nv">$textToSelectFirstLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">
            </span><span class="nv">$textToSelectLastLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">

            </span><span class="c"># If the caret is actually on the last line of the selected text, we want to select text from the caret's line upward.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$textToSelectFirstLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">
                </span><span class="nv">$textToSelectLastLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># Re-select the text, making sure the entire first and last lines are selected. +1 on EndLineWidth because column starts at 1, not 0.</span><span class="w">
            </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Select</span><span class="p">(</span><span class="nv">$textToSelectFirstLine</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">,</span><span class="w"> </span><span class="nv">$textToSelectLastLine</span><span class="p">,</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">GetLineLength</span><span class="p">(</span><span class="nv">$textToSelectLastLine</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Return the first and last line numbers selected.</span><span class="w">
    </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">PSObject</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="w">
        </span><span class="nx">FirstLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$textToSelectFirstLine</span><span class="w">
        </span><span class="nx">LastLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$textToSelectLastLine</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">CommentOrUncommentIseSelectedLines</span><span class="p">([</span><span class="n">bool</span><span class="p">]</span><span class="nv">$CommentLines</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers</span><span class="w"> </span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w">

    </span><span class="c"># If we couldn't determine which lines to select, just exit without changing anything.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># Get the text lines selected.</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$selectedTextFirstLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="o">.</span><span class="nf">FirstLineNumber</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$selectedTextLastLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="o">.</span><span class="nf">LastLineNumber</span><span class="w">

    </span><span class="c"># Get the Selected Text and convert it into an array of strings so we can easily process each line.</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$selectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$selectedTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$selectedText</span><span class="o">.</span><span class="nf">Split</span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$NEW_LINE_STRING</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringSplitOptions</span><span class="p">]::</span><span class="nx">None</span><span class="p">)</span><span class="w">

    </span><span class="c"># Process each line of the Selected Text, and save the modified lines into a text array.</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@()</span><span class="w">
    </span><span class="nv">$selectedTextArray</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kr">foreach</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="c"># If the line is not blank, add a comment character to the start of it.</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrWhiteSpace</span><span class="p">(</span><span class="nv">$lineText</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nv">$lineText</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># If we should be commenting the lines out, add a comment character to the start of the line.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$CommentLines</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w"> </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$COMMENT_STRING$lineText</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else we should be uncommenting, so remove a comment character from the start of the line if it exists.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># If the line begins with a comment, remove one (and only one) comment character.</span><span class="w">
                </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$lineText</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$COMMENT_STRING</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$lineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$lineText</span><span class="o">.</span><span class="nf">Substring</span><span class="p">(</span><span class="nv">$COMMENT_STRING</span><span class="o">.</span><span class="nf">Length</span><span class="p">)</span><span class="w">
                </span><span class="p">}</span><span class="w">
                </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nv">$lineText</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Join the text array back together to get the new Selected Text string.</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$newSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="nv">$NEW_LINE_STRING</span><span class="w">

    </span><span class="c"># Overwrite the currently Selected Text with the new Selected Text.</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">InsertText</span><span class="p">(</span><span class="nv">$newSelectedText</span><span class="p">)</span><span class="w">

    </span><span class="c"># Fully select all of the lines that were modified. +1 on End Line's Width because column starts at 1, not 0.</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Select</span><span class="p">(</span><span class="nv">$selectedTextFirstLineNumber</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">,</span><span class="w"> </span><span class="nv">$selectedTextLastLineNumber</span><span class="p">,</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">GetLineLength</span><span class="p">(</span><span class="nv">$selectedTextLastLineNumber</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Comment-IseSelectedLines</span><span class="p">([</span><span class="n">switch</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToComment</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Places a comment character at the start of each line of the selected text in the current PS ISE file.
    If no text is selected, it will comment out the line that the caret is on.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Places a comment character at the start of each line of the selected text in the current PS ISE file.
    If no text is selected, it will comment out the line that the caret is on.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DoNothingWhenNotCertainOfWhichLinesToComment
    Under the following edge case we can't determine for sure which lines in the file are selected.
    If this switch is not provided and the edge case is encountered, we will guess and attempt to comment out the selected lines, but we may guess wrong and comment out the lines above/below the selected lines.
    If this switch is provided and the edge case is encountered, no lines will be commented out.

    Edge Case:
    - When the selected text occurs multiple times in the document, directly above or below the selected text.

    Example:
    abc
    abc
    abc

    - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get commented out, so it shouldn't be a big deal.
    But if it bugs you, you can provide this switch.
#&gt;</span><span class="w">
    </span><span class="n">CommentOrUncommentIseSelectedLines</span><span class="w"> </span><span class="nt">-CommentLines</span><span class="w"> </span><span class="bp">$true</span><span class="w"> </span><span class="nt">-DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToComment</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Uncomment-IseSelectedLines</span><span class="p">([</span><span class="n">switch</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToUncomment</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
    If no text is selected, it will uncomment the line that the caret is on.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
    If no text is selected, it will uncomment the line that the caret is on.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DoNothingWhenNotCertainOfWhichLinesToUncomment
    Under the following edge case we can't determine for sure which lines in the file are selected.
    If this switch is not provided and the edge case is encountered, we will guess and attempt to uncomment the selected lines, but we may guess wrong and uncomment out the lines above/below the selected lines.
    If this switch is provided and the edge case is encountered, no lines will be uncommentet.

    Edge Case:
    - When the selected text occurs multiple times in the document, directly above or below the selected text.

    Example:
    abc
    abc
    abc

    - If only the first two lines are selected, when you run this command it may uncomment the 1st and 2nd lines correctly, or it may uncomment the 2nd and 3rd lines, depending on
    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get uncommented, so it shouldn't be a big deal.
    But if it bugs you, you can provide this switch.
#&gt;</span><span class="w">
    </span><span class="n">CommentOrUncommentIseSelectedLines</span><span class="w"> </span><span class="nt">-CommentLines</span><span class="w"> </span><span class="bp">$false</span><span class="w"> </span><span class="nt">-DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToUncomment</span><span class="w">
</span><span class="p">}</span><span class="w">


</span><span class="c">#==========================================================</span><span class="w">
</span><span class="c"># Add ISE Add-ons.</span><span class="w">
</span><span class="c">#==========================================================</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to comment all selected lines.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Comment Selected Lines"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Comment Selected Lines"</span><span class="p">,{</span><span class="n">Comment-IseSelectedLines</span><span class="p">},</span><span class="s2">"Ctrl+K"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to uncomment all selected lines.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Uncomment Selected Lines"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Uncomment Selected Lines"</span><span class="p">,{</span><span class="n">Uncomment-IseSelectedLines</span><span class="p">},</span><span class="s2">"Ctrl+Shift+K"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>As you can see by the code at the bottom, the keyboard shortcut to comment lines is <strong>Ctrl+K</strong> and to uncomment it is <strong>Ctrl+Shift+K</strong>. Feel free to change these if you like. I wanted to use the Visual Studio keyboard shortcut keys of Ctrl+K,Ctrl+C and Ctrl+K,Ctrl+U, but it looks like multi-sequence keyboard shortcuts aren’t supported. I figured that anybody who uses Visual Studio or SQL Server Management Studio would be able to stumble across this keyboard shortcut and would like it.</p>

<h3 id="ok-its-not-perfect">Ok, it’s not perfect</h3>

<p>If you’re still reading then you deserve to know about the edge case bug with my implementation. If you actually read through the functions’ documentation in the code you will see this mentioned there as well.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Edge Case:
    - When the selected text occurs multiple times in the document, directly above or below the selected text.

    Example:
    abc
    abc
    abc

    - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get uncommented, so it shouldn't be a big deal.
</code></pre></div></div>

<p>Basically the problem is that I change the selected text to ensure that the entire lines are selected (so that I can put the comment character at the start of the line). The PS ISE API doesn’t tell me the selected text’s starting and ending lines, so I have to try and infer it from the line the caret is on, but the caret can be on either the first or the last line of the selected text. So if text that is identical to the selected text appears directly above or below the selected text, I can’t know for sure if the caret is on the first line of the selected text, or the last line, so I just make a guess. If this bothers you there is a switch you can provide so that it won’t comment out any lines at all if this edge case is hit.</p>

<h2 id="show-me-all-the-code">Show Me ALL The Code</h2>

<p>Ok, so I mentioned a couple other must-have ISE add-ons above. Here’s the code to add to your ISE profile that includes my comment/uncomment code, as well as the Save All files and Save/Restore ISE State functionality provided by <a href="http://blogs.msdn.com/b/powershell/">the PowerShell Team</a>. This includes a couple customizations that I made; namely adding a Save ISE State And Exit command (Alt+Shift+E) and having the ISE State automatically load when PS ISE starts (I didn’t change the functions they provided that do the actual work at all). So if you want your last session to be automatically reloaded, you just have to get in the habit of closing ISE with Alt+Shift+E (again, you can change this keyboard shortcut if you want).</p>

<p><a href="/assets/Posts/2013/09/CommentSelectedLinesPlusOthers.zip">Download The Code</a></p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#==========================================================</span><span class="w">
</span><span class="c"># Functions used by the script.</span><span class="w">
</span><span class="c">#==========================================================</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Save-AllISEFiles</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
</span><span class="cs">.SYNOPSIS</span><span class="cm">
    Saves all ISE Files except for untitled files. If You have multiple PowerShellTabs, saves files in all tabs.
#&gt;</span><span class="w">
    </span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$tab</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">PowerShellTabs</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$file</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$tab</span><span class="o">.</span><span class="nf">Files</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="kr">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$file</span><span class="o">.</span><span class="nf">IsUntitled</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$file</span><span class="o">.</span><span class="nf">Save</span><span class="p">()</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Export-ISEState</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
</span><span class="cs">.SYNOPSIS</span><span class="cm">
    Stores the opened files in a serialized xml so that later the same set can be opened

</span><span class="cs">.DESCRIPTION</span><span class="cm">
    Creates an xml file with all PowerShell tabs and file information

</span><span class="cs">.PARAMETER</span><span class="cm"> fileName
    The name of the project to create a new version from. This will also be the name of the new project, but with a different version

</span><span class="cs">.EXAMPLE</span><span class="cm">
    Stores current state into c:\temp\files.isexml
    Export-ISEState c:\temp\files.isexml
#&gt;</span><span class="w">

    </span><span class="kr">Param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$fileName</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="c"># We are exporting a "tree" worth of information like this:</span><span class="w">
    </span><span class="c">#</span><span class="w">
    </span><span class="c">#  SelectedTabDisplayName: PowerShellTab 1</span><span class="w">
    </span><span class="c">#  SelectedFilePath: c:\temp\a.ps1</span><span class="w">
    </span><span class="c">#  TabInformation:</span><span class="w">
    </span><span class="c">#      PowerShellTab 1:</span><span class="w">
    </span><span class="c">#           File 1:</span><span class="w">
    </span><span class="c">#                FullPath:     c:\temp\a.ps1</span><span class="w">
    </span><span class="c">#                FileContents: $null</span><span class="w">
    </span><span class="c">#           File 2:</span><span class="w">
    </span><span class="c">#                FullPath:     Untitled.ps1</span><span class="w">
    </span><span class="c">#                FileContents: $a=0...</span><span class="w">
    </span><span class="c">#       PowerShellTab 2:</span><span class="w">
    </span><span class="c">#       ...</span><span class="w">
    </span><span class="c">#  Hashtables and arraylists serialize rather well with export-clixml</span><span class="w">
    </span><span class="c">#  We will keep the list of PowerShellTabs in one ArrayList and the list of files</span><span class="w">
    </span><span class="c">#  and contents(for untitled files) inside each tab in a couple of ArrayList.</span><span class="w">
    </span><span class="c">#  We will use Hashtables to group the information.</span><span class="w">
    </span><span class="nv">$tabs</span><span class="o">=</span><span class="n">new-object</span><span class="w"> </span><span class="nx">collections.arraylist</span><span class="w">

    </span><span class="c"># before getting file information, save all untitled files to make sure their latest</span><span class="w">
    </span><span class="c"># text is on disk</span><span class="w">
    </span><span class="n">Save-AllISEFiles</span><span class="w">

    </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$tab</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">PowerShellTabs</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$files</span><span class="o">=</span><span class="n">new-object</span><span class="w"> </span><span class="nx">collections.arraylist</span><span class="w">
        </span><span class="nv">$filesContents</span><span class="o">=</span><span class="n">new-object</span><span class="w"> </span><span class="nx">collections.arraylist</span><span class="w">
        </span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$file</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$tab</span><span class="o">.</span><span class="nf">Files</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># $null = will avoid $files.Add from showing in the output</span><span class="w">
            </span><span class="bp">$null</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$files</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$file</span><span class="o">.</span><span class="nf">FullPath</span><span class="p">)</span><span class="w">

            </span><span class="kr">if</span><span class="p">(</span><span class="nv">$file</span><span class="o">.</span><span class="nf">IsUntitled</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># untitled files are not yet on disk so we will save the file contents inside the xml</span><span class="w">
                </span><span class="c"># export-clixml performs the appropriate escaping for the contents to be inside the xml</span><span class="w">
                </span><span class="bp">$null</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$filesContents</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$file</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Text</span><span class="p">)</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># titled files get their content from disk</span><span class="w">
                </span><span class="bp">$null</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$filesContents</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="bp">$null</span><span class="p">)</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="nv">$simpleTab</span><span class="o">=</span><span class="n">new-object</span><span class="w"> </span><span class="nx">collections.hashtable</span><span class="w">

        </span><span class="c"># The DisplayName of a PowerShellTab can only be change with scripting</span><span class="w">
        </span><span class="c"># we want to maintain the chosen name</span><span class="w">
        </span><span class="nv">$simpleTab</span><span class="p">[</span><span class="s2">"DisplayName"</span><span class="p">]</span><span class="o">=</span><span class="nv">$tab</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w">

        </span><span class="c"># $files and $filesContents is the information gathered in the foreach $file above</span><span class="w">
        </span><span class="nv">$simpleTab</span><span class="p">[</span><span class="s2">"Files"</span><span class="p">]</span><span class="o">=</span><span class="nv">$files</span><span class="w">
        </span><span class="nv">$simpleTab</span><span class="p">[</span><span class="s2">"FilesContents"</span><span class="p">]</span><span class="o">=</span><span class="nv">$filesContents</span><span class="w">

        </span><span class="c"># add to the list of tabs</span><span class="w">
        </span><span class="bp">$null</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$tabs</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$simpleTab</span><span class="p">)</span><span class="w">

    </span><span class="p">}</span><span class="w">

    </span><span class="c"># tabsToSerialize will be a hashtable with all the information we want</span><span class="w">
    </span><span class="c"># it is the "root" of the information to be serialized in the hashtable we store...</span><span class="w">
    </span><span class="nv">$tabToSerialize</span><span class="o">=</span><span class="n">new-object</span><span class="w"> </span><span class="nx">collections.hashtable</span><span class="w">

    </span><span class="c"># the $tabs information gathered in the foreach $tab above...</span><span class="w">
    </span><span class="nv">$tabToSerialize</span><span class="p">[</span><span class="s2">"TabInformation"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$tabs</span><span class="w">

    </span><span class="c"># ...and the selected tab and file.</span><span class="w">
    </span><span class="nv">$tabToSerialize</span><span class="p">[</span><span class="s2">"SelectedTabDisplayName"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w">
    </span><span class="nv">$tabToSerialize</span><span class="p">[</span><span class="s2">"SelectedFilePath"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">FullPath</span><span class="w">

    </span><span class="c"># now we just export it to $fileName</span><span class="w">
    </span><span class="nv">$tabToSerialize</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">export-clixml</span><span class="w"> </span><span class="nt">-path</span><span class="w"> </span><span class="nv">$fileName</span><span class="w">
</span><span class="p">}</span><span class="w">


</span><span class="kr">function</span><span class="w"> </span><span class="nf">Import-ISEState</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
</span><span class="cs">.SYNOPSIS</span><span class="cm">
    Reads a file with ISE state information about which files to open and opens them

</span><span class="cs">.DESCRIPTION</span><span class="cm">
    Reads a file created by Export-ISEState with the PowerShell tabs and files to open

</span><span class="cs">.PARAMETER</span><span class="cm"> fileName
    The name of the file created with Export-ISEState

</span><span class="cs">.EXAMPLE</span><span class="cm">
    Restores current state from c:\temp\files.isexml
    Import-ISEState c:\temp\files.isexml
#&gt;</span><span class="w">

    </span><span class="kr">Param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$fileName</span><span class="w">
    </span><span class="p">)</span><span class="w">


    </span><span class="c"># currentTabs is used to keep track of the tabs currently opened.</span><span class="w">
    </span><span class="c"># If "PowerShellTab 1" is opened and $fileName contains files for it, we</span><span class="w">
    </span><span class="c"># want to open them in "PowerShellTab 1"</span><span class="w">
    </span><span class="nv">$currentTabs</span><span class="o">=</span><span class="n">new-object</span><span class="w"> </span><span class="nx">collections.hashtable</span><span class="w">
    </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$tab</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">PowerShellTabs</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$currentTabs</span><span class="p">[</span><span class="nv">$tab</span><span class="o">.</span><span class="nf">DisplayName</span><span class="p">]</span><span class="o">=</span><span class="nv">$tab</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="nv">$tabs</span><span class="o">=</span><span class="n">import-cliXml</span><span class="w"> </span><span class="nt">-path</span><span class="w"> </span><span class="nv">$fileName</span><span class="w">

    </span><span class="c"># those will keep track of selected tab and files</span><span class="w">
    </span><span class="nv">$selectedTab</span><span class="o">=</span><span class="bp">$null</span><span class="w">
    </span><span class="nv">$selectedFile</span><span class="o">=</span><span class="bp">$null</span><span class="w">

    </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$tab</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$tabs</span><span class="o">.</span><span class="nf">TabInformation</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$newTab</span><span class="o">=</span><span class="nv">$currentTabs</span><span class="p">[</span><span class="nv">$tab</span><span class="o">.</span><span class="nf">DisplayName</span><span class="p">]</span><span class="w">
        </span><span class="kr">if</span><span class="p">(</span><span class="nv">$newTab</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$newTab</span><span class="o">=</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">PowerShellTabs</span><span class="o">.</span><span class="nf">Add</span><span class="p">()</span><span class="w">
            </span><span class="nv">$newTab</span><span class="o">.</span><span class="nf">DisplayName</span><span class="o">=</span><span class="nv">$tab</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="c">#newTab now has a brand new or a previouslly existing PowerShell tab with the same name as the one in the file</span><span class="w">

        </span><span class="c"># if the tab is the selected tab save it for later selection</span><span class="w">
        </span><span class="kr">if</span><span class="p">(</span><span class="nv">$newTab</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$tabs</span><span class="o">.</span><span class="nf">SelectedTabDisplayName</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$selectedTab</span><span class="o">=</span><span class="nv">$newTab</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># currentUntitledFileContents keeps track of the contents for untitled files</span><span class="w">
        </span><span class="c"># if you already have the content in one of your untitled files</span><span class="w">
        </span><span class="c"># there is no reason to add the same content again</span><span class="w">
        </span><span class="c"># this will make sure calling import-ISEState multiple times</span><span class="w">
        </span><span class="c"># does not keep on adding untitled files</span><span class="w">
        </span><span class="nv">$currentUntitledFileContents</span><span class="o">=</span><span class="n">new-object</span><span class="w"> </span><span class="nx">collections.hashtable</span><span class="w">
        </span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$newTabFile</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$newTab</span><span class="o">.</span><span class="nf">Files</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="kr">if</span><span class="p">(</span><span class="nv">$newTabFile</span><span class="o">.</span><span class="nf">IsUntitled</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$currentUntitledFileContents</span><span class="p">[</span><span class="nv">$newTabFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Text</span><span class="p">]</span><span class="o">=</span><span class="nv">$newTabFile</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># since we will want both file and fileContents we need to use a for instead of a foreach</span><span class="w">
        </span><span class="kr">for</span><span class="p">(</span><span class="nv">$i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nv">$i</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$tab</span><span class="o">.</span><span class="nf">Files</span><span class="o">.</span><span class="nf">Count</span><span class="p">;</span><span class="nv">$i</span><span class="o">++</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$tab</span><span class="o">.</span><span class="n">Files</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span><span class="w">
            </span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$tab</span><span class="o">.</span><span class="n">FilesContents</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span><span class="w">

            </span><span class="c">#fileContents will be $null for titled files</span><span class="w">
            </span><span class="kr">if</span><span class="p">(</span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># the overload of Add taking one string opens the file identified by the string</span><span class="w">
                </span><span class="nv">$newFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$newTab</span><span class="o">.</span><span class="nf">Files</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$file</span><span class="p">)</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="kr">else</span><span class="w"> </span><span class="c"># the file is untitled</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c">#see if the content is already present in $newTab</span><span class="w">
                </span><span class="nv">$newFile</span><span class="o">=</span><span class="nv">$currentUntitledFileContents</span><span class="p">[</span><span class="nv">$fileContents</span><span class="p">]</span><span class="w">

                </span><span class="kr">if</span><span class="p">(</span><span class="nv">$newFile</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="c"># the overload of Add taking no arguments creates a new untitled file</span><span class="w">
                    </span><span class="c"># The number for untitled files is determined by the application so we</span><span class="w">
                    </span><span class="c"># don't try to keep the untitled number, we just create a new untitled.</span><span class="w">
                    </span><span class="nv">$newFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$newTab</span><span class="o">.</span><span class="nf">Files</span><span class="o">.</span><span class="nf">Add</span><span class="p">()</span><span class="w">

                    </span><span class="c"># and here we restore the contents</span><span class="w">
                    </span><span class="nv">$newFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Text</span><span class="o">=</span><span class="nv">$fileContents</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># if the file is the selected file in the selected tab save it for later selection</span><span class="w">
            </span><span class="kr">if</span><span class="p">((</span><span class="nv">$selectedTab</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$newTab</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="nv">$tabs</span><span class="o">.</span><span class="nf">SelectedFilePath</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$file</span><span class="p">))</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$selectedFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$newFile</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c">#finally we selected the PowerShellTab that was selected and the file that was selected on it.</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">PowerShellTabs</span><span class="o">.</span><span class="nf">SetSelectedPowerShellTab</span><span class="p">(</span><span class="nv">$selectedTab</span><span class="p">)</span><span class="w">
    </span><span class="kr">if</span><span class="p">(</span><span class="nv">$selectedFile</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$selectedTab</span><span class="o">.</span><span class="nf">Files</span><span class="o">.</span><span class="nf">SetSelectedFile</span><span class="p">(</span><span class="nv">$selectedFile</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Define our constant variables.</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NEW_LINE_STRING</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="se">`r`n</span><span class="s2">"</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$COMMENT_STRING</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"#"</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers</span><span class="p">([</span><span class="n">bool</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Exands the selected text to make sure the entire lines are selected.
    Returns $null if we can't determine with certainty which lines to select and the

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Exands the selected text to make sure the entire lines are selected.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DoNothingWhenNotCertainOfWhichLinesToSelect
    Under the following edge case we can't determine for sure which lines in the file are selected.
    If this switch is not provided and the edge case is encountered, we will guess and attempt to select the entire selected lines, but we may guess wrong and select the lines above/below the selected lines.
    If this switch is provided and the edge case is encountered, no lines will be selected.

    Edge Case:
    - When the selected text occurs multiple times in the document, directly above or below the selected text.

    Example:
    abc
    abc
    abc

    - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get selected, so it shouldn't be a big deal.
    But if it bugs you, you can provide this switch.

    .OUTPUT
    PSObject. Returns a PSObject with the properties FirstLineNumber and LastLineNumber, which correspond to the first and last line numbers of the selected text.
#&gt;</span><span class="w">

    </span><span class="c"># Backup all of the original info before we modify it.</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">CaretLine</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$originalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$originalCaretLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">CaretLineText</span><span class="w">

    </span><span class="c"># Assume only one line is selected.</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$textToSelectFirstLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$textToSelectLastLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">

    </span><span class="c">#------------------------</span><span class="w">
    </span><span class="c"># Before we process the selected text, we need to make sure all selected lines are fully selected (i.e. the entire line is selected).</span><span class="w">
    </span><span class="c">#------------------------</span><span class="w">

    </span><span class="c"># If no text is selected, OR only part of one line is selected (and it doesn't include the start of the line), select the entire line that the caret is currently on.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="o">-le</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="o">!</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="o">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nv">$NEW_LINE_STRING</span><span class="p">))</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectCaretLine</span><span class="p">()</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="c"># Else the first part of one line (or the entire line), or multiple lines are selected.</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># Get the number of lines in the originally selected text.</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedText</span><span class="o">.</span><span class="nf">Split</span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$NEW_LINE_STRING</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringSplitOptions</span><span class="p">]::</span><span class="nx">None</span><span class="p">)</span><span class="w">
        </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="o">.</span><span class="nf">Length</span><span class="w">

        </span><span class="c"># If only one line is selected, make sure it is fully selected.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-le</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectCaretLine</span><span class="p">()</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="c"># Else there are multiple lines selected, so make sure the first character of the top line is selected (so that we put the comment character at the start of the top line, not in the middle).</span><span class="w">
        </span><span class="c"># The first character of the bottom line will always be selected when multiple lines are selected, so we don't have to worry about making sure it is selected; only the top line.</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># Determine if the caret is on the first or last line of the selected text.</span><span class="w">
            </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$firstLineOfOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w">
            </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lastLineOfOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalSelectedTextArray</span><span class="p">[</span><span class="nv">$originalSelectedTextArray</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">]</span><span class="w">

            </span><span class="c"># If the caret is definitely on the first line.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="nv">$firstLineOfOriginalSelectedText</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$lastLineOfOriginalSelectedText</span><span class="p">))</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else if the caret is definitely on the last line.</span><span class="w">
            </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$lastLineOfOriginalSelectedText</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$originalCaretLineText</span><span class="o">.</span><span class="nf">EndsWith</span><span class="p">(</span><span class="nv">$firstLineOfOriginalSelectedText</span><span class="p">))</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else we need to do further analysis to determine if the caret is on the first or last line of the selected text.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$numberOfLinesInFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">LineCount</span><span class="w">

                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$caretOnFirstLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">Empty</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because array starts at 0 and file lines start at 1.</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnFirstLineArrayStopIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">

                </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$caretOnLastLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">Empty</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnLastLineArrayStopIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">  </span><span class="c"># -1 because array starts at 0 and file lines start at 1.</span><span class="w">
                </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$caretOnLastLineArrayStartIndex</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnLastLineArrayStopIndex</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">

                </span><span class="c"># If the caret being on the first line would cause us to go "off the file", then we know the caret is on the last line.</span><span class="w">
                </span><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnFirstLineArrayStopIndex</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$numberOfLinesInFile</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
                </span><span class="p">}</span><span class="w">
                </span><span class="c"># If the caret being on the last line would cause us to go "off the file", then we know the caret is on the first line.</span><span class="w">
                </span><span class="kr">elseif</span><span class="w"> </span><span class="p">((</span><span class="nv">$caretOnLastLineArrayStartIndex</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnLastLineArrayStopIndex</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$numberOfLinesInFile</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
                </span><span class="p">}</span><span class="w">
                </span><span class="c"># Else we still don't know where the caret is.</span><span class="w">
                </span><span class="kr">else</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$filesTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Text</span><span class="o">.</span><span class="nf">Split</span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$NEW_LINE_STRING</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringSplitOptions</span><span class="p">]::</span><span class="nx">None</span><span class="p">)</span><span class="w">

                    </span><span class="c"># Get the text of the lines where the caret is on the first line of the selected text.</span><span class="w">
                    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$caretOnFirstLineTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@([</span><span class="n">string</span><span class="p">]</span><span class="err">::Empty</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="c"># Declare an array with the number of elements required.</span><span class="w">
                    </span><span class="p">[</span><span class="n">System.Array</span><span class="p">]::</span><span class="n">Copy</span><span class="p">(</span><span class="nv">$filesTextArray</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnFirstLineArrayStartIndex</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnFirstLineTextArray</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="nv">$caretOnFirstLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnFirstLineTextArray</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="nv">$NEW_LINE_STRING</span><span class="w">

                    </span><span class="c"># Get the text of the lines where the caret is on the last line of the selected text.</span><span class="w">
                    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$caretOnLastLineTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@([</span><span class="n">string</span><span class="p">]</span><span class="err">::Empty</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="c"># Declare an array with the number of elements required.</span><span class="w">
                    </span><span class="p">[</span><span class="n">System.Array</span><span class="p">]::</span><span class="n">Copy</span><span class="p">(</span><span class="nv">$filesTextArray</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnLastLineArrayStartIndex</span><span class="p">,</span><span class="w"> </span><span class="nv">$caretOnLastLineTextArray</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nv">$numberOfLinesInSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="nv">$caretOnLastLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnLastLineTextArray</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="nv">$NEW_LINE_STRING</span><span class="w">

                    </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnFirstLineText</span><span class="o">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nv">$originalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$caretOnLastLineText</span><span class="o">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nv">$originalSelectedText</span><span class="p">)</span><span class="w">

                    </span><span class="c"># If the selected text is only within the text of when the caret is on the first line, then we know for sure the caret is on the first line.</span><span class="w">
                    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                    </span><span class="c"># Else if the selected text is only within the text of when the caret is on the last line, then we know for sure the caret is on the last line.</span><span class="w">
                    </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="o">!</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                    </span><span class="c"># Else if the selected text is in both sets of text, then we don't know for sure if the caret is on the first or last line.</span><span class="w">
                    </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$caretOnFirstLineTextContainsOriginalSelectedText</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$caretOnLastLineTextContainsOriginalSelectedText</span><span class="p">)</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="c"># If we shouldn't do anything since we might comment out text that is not selected by the user, just exit this function and return null.</span><span class="w">
                        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="p">)</span><span class="w">
                        </span><span class="p">{</span><span class="w">
                            </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w">
                        </span><span class="p">}</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                    </span><span class="c"># Else something went wrong and there is a flaw in this logic, since the selected text should be in one of our two strings, so let's just guess!</span><span class="w">
                    </span><span class="kr">else</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="n">Write-Error</span><span class="w"> </span><span class="s2">"WHAT HAPPENED?!?! This line should never be reached. There is a flaw in our logic!"</span><span class="w">
                        </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># Assume the caret is on the first line of the selected text, so we want to select text from the caret's line downward.</span><span class="w">
            </span><span class="nv">$textToSelectFirstLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">
            </span><span class="nv">$textToSelectLastLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">

            </span><span class="c"># If the caret is actually on the last line of the selected text, we want to select text from the caret's line upward.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$isCaretOnFirstLineOfSelectedText</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$textToSelectFirstLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="nv">$numberOfLinesInSelectedText</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="c"># -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).</span><span class="w">
                </span><span class="nv">$textToSelectLastLine</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$originalCaretLine</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># Re-select the text, making sure the entire first and last lines are selected. +1 on EndLineWidth because column starts at 1, not 0.</span><span class="w">
            </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Select</span><span class="p">(</span><span class="nv">$textToSelectFirstLine</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">,</span><span class="w"> </span><span class="nv">$textToSelectLastLine</span><span class="p">,</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">GetLineLength</span><span class="p">(</span><span class="nv">$textToSelectLastLine</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Return the first and last line numbers selected.</span><span class="w">
    </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">PSObject</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="w">
        </span><span class="nx">FirstLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$textToSelectFirstLine</span><span class="w">
        </span><span class="nx">LastLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$textToSelectLastLine</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">CommentOrUncommentIseSelectedLines</span><span class="p">([</span><span class="n">bool</span><span class="p">]</span><span class="nv">$CommentLines</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">bool</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers</span><span class="w"> </span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w">

    </span><span class="c"># If we couldn't determine which lines to select, just exit without changing anything.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># Get the text lines selected.</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$selectedTextFirstLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="o">.</span><span class="nf">FirstLineNumber</span><span class="w">
    </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$selectedTextLastLineNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$selectedTextFirstAndLastLineNumbers</span><span class="o">.</span><span class="nf">LastLineNumber</span><span class="w">

    </span><span class="c"># Get the Selected Text and convert it into an array of strings so we can easily process each line.</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$selectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">SelectedText</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="w"> </span><span class="nv">$selectedTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$selectedText</span><span class="o">.</span><span class="nf">Split</span><span class="p">([</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$NEW_LINE_STRING</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">StringSplitOptions</span><span class="p">]::</span><span class="nx">None</span><span class="p">)</span><span class="w">

    </span><span class="c"># Process each line of the Selected Text, and save the modified lines into a text array.</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">[]]</span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@()</span><span class="w">
    </span><span class="nv">$selectedTextArray</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kr">foreach</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="c"># If the line is not blank, add a comment character to the start of it.</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$lineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrWhiteSpace</span><span class="p">(</span><span class="nv">$lineText</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nv">$lineText</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># If we should be commenting the lines out, add a comment character to the start of the line.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$CommentLines</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w"> </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$COMMENT_STRING$lineText</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else we should be uncommenting, so remove a comment character from the start of the line if it exists.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># If the line begins with a comment, remove one (and only one) comment character.</span><span class="w">
                </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$lineText</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="nv">$COMMENT_STRING</span><span class="p">))</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nv">$lineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$lineText</span><span class="o">.</span><span class="nf">Substring</span><span class="p">(</span><span class="nv">$COMMENT_STRING</span><span class="o">.</span><span class="nf">Length</span><span class="p">)</span><span class="w">
                </span><span class="p">}</span><span class="w">
                </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nv">$lineText</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Join the text array back together to get the new Selected Text string.</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$newSelectedText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$newSelectedTextArray</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="nv">$NEW_LINE_STRING</span><span class="w">

    </span><span class="c"># Overwrite the currently Selected Text with the new Selected Text.</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">InsertText</span><span class="p">(</span><span class="nv">$newSelectedText</span><span class="p">)</span><span class="w">

    </span><span class="c"># Fully select all of the lines that were modified. +1 on End Line's Width because column starts at 1, not 0.</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">Select</span><span class="p">(</span><span class="nv">$selectedTextFirstLineNumber</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">,</span><span class="w"> </span><span class="nv">$selectedTextLastLineNumber</span><span class="p">,</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentFile</span><span class="o">.</span><span class="nf">Editor</span><span class="o">.</span><span class="nf">GetLineLength</span><span class="p">(</span><span class="nv">$selectedTextLastLineNumber</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Comment-IseSelectedLines</span><span class="p">([</span><span class="n">switch</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToComment</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Places a comment character at the start of each line of the selected text in the current PS ISE file.
    If no text is selected, it will comment out the line that the caret is on.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Places a comment character at the start of each line of the selected text in the current PS ISE file.
    If no text is selected, it will comment out the line that the caret is on.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DoNothingWhenNotCertainOfWhichLinesToComment
    Under the following edge case we can't determine for sure which lines in the file are selected.
    If this switch is not provided and the edge case is encountered, we will guess and attempt to comment out the selected lines, but we may guess wrong and comment out the lines above/below the selected lines.
    If this switch is provided and the edge case is encountered, no lines will be commented out.

    Edge Case:
    - When the selected text occurs multiple times in the document, directly above or below the selected text.

    Example:
    abc
    abc
    abc

    - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get commented out, so it shouldn't be a big deal.
    But if it bugs you, you can provide this switch.
#&gt;</span><span class="w">
    </span><span class="n">CommentOrUncommentIseSelectedLines</span><span class="w"> </span><span class="nt">-CommentLines</span><span class="w"> </span><span class="bp">$true</span><span class="w"> </span><span class="nt">-DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToComment</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Uncomment-IseSelectedLines</span><span class="p">([</span><span class="n">switch</span><span class="p">]</span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToUncomment</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
    If no text is selected, it will uncomment the line that the caret is on.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
    If no text is selected, it will uncomment the line that the caret is on.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DoNothingWhenNotCertainOfWhichLinesToUncomment
    Under the following edge case we can't determine for sure which lines in the file are selected.
    If this switch is not provided and the edge case is encountered, we will guess and attempt to uncomment the selected lines, but we may guess wrong and uncomment out the lines above/below the selected lines.
    If this switch is provided and the edge case is encountered, no lines will be uncommentet.

    Edge Case:
    - When the selected text occurs multiple times in the document, directly above or below the selected text.

    Example:
    abc
    abc
    abc

    - If only the first two lines are selected, when you run this command it may uncomment the 1st and 2nd lines correctly, or it may uncomment the 2nd and 3rd lines, depending on
    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get uncommented, so it shouldn't be a big deal.
    But if it bugs you, you can provide this switch.
#&gt;</span><span class="w">
    </span><span class="n">CommentOrUncommentIseSelectedLines</span><span class="w"> </span><span class="nt">-CommentLines</span><span class="w"> </span><span class="bp">$false</span><span class="w"> </span><span class="nt">-DoNothingWhenNotCertainOfWhichLinesToSelect</span><span class="w"> </span><span class="nv">$DoNothingWhenNotCertainOfWhichLinesToUncomment</span><span class="w">
</span><span class="p">}</span><span class="w">


</span><span class="c">#==========================================================</span><span class="w">
</span><span class="c"># Add ISE Add-ons.</span><span class="w">
</span><span class="c">#==========================================================</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to save all files.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Save All"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Save All"</span><span class="p">,{</span><span class="n">Save-AllISEFiles</span><span class="p">},</span><span class="s2">"Ctrl+Shift+S"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$ISE_STATE_FILE_PATH</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="p">(</span><span class="n">Split-Path</span><span class="w"> </span><span class="nv">$profile</span><span class="w"> </span><span class="nt">-Parent</span><span class="p">)</span><span class="w"> </span><span class="s2">"IseState.xml"</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to export the current ISE state.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Save ISE State"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Save ISE State"</span><span class="p">,{</span><span class="n">Export-ISEState</span><span class="w"> </span><span class="nv">$ISE_STATE_FILE_PATH</span><span class="p">},</span><span class="s2">"Alt+Shift+S"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to export the current ISE state and exit.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Save ISE State And Exit"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Save ISE State And Exit"</span><span class="p">,{</span><span class="n">Export-ISEState</span><span class="w"> </span><span class="nv">$ISE_STATE_FILE_PATH</span><span class="p">;</span><span class="w"> </span><span class="kr">exit</span><span class="p">},</span><span class="s2">"Alt+Shift+E"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to import the ISE state.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Load ISE State"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Load ISE State"</span><span class="p">,{</span><span class="n">Import-ISEState</span><span class="w"> </span><span class="nv">$ISE_STATE_FILE_PATH</span><span class="p">},</span><span class="s2">"Alt+Shift+L"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to comment all selected lines.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Comment Selected Lines"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Comment Selected Lines"</span><span class="p">,{</span><span class="n">Comment-IseSelectedLines</span><span class="p">},</span><span class="s2">"Ctrl+K"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Add a new option in the Add-ons menu to uncomment all selected lines.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">DisplayName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Uncomment Selected Lines"</span><span class="w"> </span><span class="p">}))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">AddOnsMenu</span><span class="o">.</span><span class="nf">Submenus</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="s2">"Uncomment Selected Lines"</span><span class="p">,{</span><span class="n">Uncomment-IseSelectedLines</span><span class="p">},</span><span class="s2">"Ctrl+Shift+K"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c">#==========================================================</span><span class="w">
</span><span class="c"># Perform script tasks.</span><span class="w">
</span><span class="c">#==========================================================</span><span class="w">

</span><span class="c"># Automatically load our saved session if we just opened ISE and have a default blank session.</span><span class="w">
</span><span class="c"># Because this may remove the default "Untitled1.ps1" file, try and have this execute before any other code so the file is removed before the user can start typing in it.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">PowerShellTabs</span><span class="o">.</span><span class="nf">Count</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">Files</span><span class="o">.</span><span class="nf">Count</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="n">Files</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">IsUntitled</span><span class="p">))</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Remove the default "Untitled1.ps1" file and then load the session.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="n">Files</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">IsRecovered</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$psISE</span><span class="o">.</span><span class="nf">CurrentPowerShellTab</span><span class="o">.</span><span class="nf">Files</span><span class="o">.</span><span class="nf">RemoveAt</span><span class="p">(</span><span class="nx">0</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="n">Import-ISEState</span><span class="w"> </span><span class="nv">$ISE_STATE_FILE_PATH</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Clear the screen so we don't see any output when opening a new session.</span><span class="w">
</span><span class="n">Clear-Host</span><span class="w">
</span></code></pre></div></div>

<p>Hopefully this post makes your ISE experience a little better. Feel free to comment and let me know if you like this or find any problems with it. Know of any other must-have ISE add-ons? Let me know.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Productivity" /><category term="Addon" /><category term="Comment" /><category term="ISE" /><category term="keyboard shortcuts" /><category term="multi line" /><category term="multiline" /><category term="PowerShell" /><category term="PowerShell ISE" /><category term="Profile" /><category term="PS" /><category term="PS ISE" /><category term="Uncomment" /><category term="Windows PowerShell ISE" /><summary type="html"><![CDATA[I’ve written some code that you can add to your ISE profile that adds keyboard shortcuts to quickly comment and uncomment lines in PowerShell ISE. So you can quickly turn this:]]></summary></entry><entry><title type="html">Free Xbox 360 Games If You Have Xbox Live Gold Subscription</title><link href="https://blog.danskingdom.com/free-xbox-360-games-if-you-have-xbox-live-gold-subscription/" rel="alternate" type="text/html" title="Free Xbox 360 Games If You Have Xbox Live Gold Subscription" /><published>2013-06-12T17:03:18+00:00</published><updated>2013-06-12T17:03:18+00:00</updated><id>https://blog.danskingdom.com/free-xbox-360-games-if-you-have-xbox-live-gold-subscription</id><content type="html" xml:base="https://blog.danskingdom.com/free-xbox-360-games-if-you-have-xbox-live-gold-subscription/"><![CDATA[<p>I know this isn’t programming related (and thus doesn’t quite go with the theme of my blog), but I thought I’d share an Pass this along to anybody you know that has an Xbox 360.</p>

<p>At <a href="http://www.theverge.com/2013/6/10/4413668/xbox-live-gold-free-games-promotion-e3-2013">E3 Microsoft announced</a> that it would <a href="http://techcrunch.com/2013/06/10/microsoft-fires-back-at-sony-with-free-game-downloads-for-xbox-live-gold-gamers/">start giving away 2 free games a month to Xbox Live Gold subscribers</a>.</p>

<p>On July 1st Assassin’s Creed 2 and Halo 3 will be available to download for free.</p>

<p><strong>Right now</strong> you can go download Fable 3 for free. I downloaded it last night, so it’s legit. Also, I’m in Canada so it looks like this is available to more markets than just the US. Woot!</p>

<p><strong>To download the games, from your 360 dashboard go to:</strong> Games menu, Browse Games, Featured menu, Free with gold, [game] (in this case Fable 3).</p>

<p>The free games are supposed to rotate every month, so you have to get them while they are still available. The XBox page says that they will be making a new game available for free on the 1st and 16th of every month, so do I like did and make some calendar reminders for yourself to get the games for free while they are still available so that you don’t forget.</p>

<p>The games are only available in download format though, so if you only have the small 20GB hard drive (HD) or your HD is almost full, you might want to be selective about which games you get. Fable 3 is about 5.5GB. Also, if you delete the game from your HD down the road, you won’t be able to download it for free again. You can purchase <a href="http://en.wikipedia.org/wiki/List_of_Xbox_360_accessories#Detachable_hard_drives">larger hard drives for your 360</a> if you want, or use a USB mass storage device (up to 32GB) to store your games on (I guess not all USB sticks are compatible with the 360; <a href="http://blog.yellowchilli.net/2010/04/xbox-360-usb-stick-flash-drive.html">here’s a compatibility list I found</a>).</p>

<p>An <a href="http://www.xbox.com/en-CA/Live?xr=shellnav">Xbox Live Gold subscription</a> is about $5/month, so it pretty much pays for itself now by getting 2 free games a month.</p>

<p>I remember reading somewhere that they would only be releasing the 2 free games a month until the <a href="http://en.wikipedia.org/wiki/Xbox_One">Xbox One</a> comes out in November 2013, but I can’t find that source now, so I’m not sure if that’s true or not.</p>

<p>If your dad is a gamer this tidbit could be your poor-man’s father’s day gift to him :P</p>

<p>It has also been brought to my attention that <a href="http://us.playstation.com/psn/playstation-plus/">Sony has been offering something similar for PS+ subscribers for over a year now</a>, where they get free games each month for PS3, PSP, and PS Vita. The difference is that you need to be a PS+ subscriber in order to play the free games, where with the Xbox once you download the game it is yours regardless of if you discontinue your Xbox Live Gold subscription.</p>

<p>Happy gaming!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Xbox" /><category term="360" /><category term="Free" /><category term="Games" /><category term="Gold" /><category term="Live" /><category term="Subscription" /><category term="Xbox" /><category term="Xbox 360" /><summary type="html"><![CDATA[I know this isn’t programming related (and thus doesn’t quite go with the theme of my blog), but I thought I’d share an Pass this along to anybody you know that has an Xbox 360.]]></summary></entry><entry><title type="html">Create and publish your NuGet package in one click with the New-NuGetPackage PowerShell script</title><link href="https://blog.danskingdom.com/create-and-publish-your-nuget-package-in-one-click-with-the-new-nugetpackage-powershell-script/" rel="alternate" type="text/html" title="Create and publish your NuGet package in one click with the New-NuGetPackage PowerShell script" /><published>2013-06-07T20:54:04+00:00</published><updated>2013-06-07T20:54:04+00:00</updated><id>https://blog.danskingdom.com/create-and-publish-your-nuget-package-in-one-click-with-the-new-nugetpackage-powershell-script</id><content type="html" xml:base="https://blog.danskingdom.com/create-and-publish-your-nuget-package-in-one-click-with-the-new-nugetpackage-powershell-script/"><![CDATA[<p>I’ve spent a good chunk of time investigating how NuGet.exe works and creating <a href="https://newnugetpackage.codeplex.com/">a PowerShell script called New-NuGetPackage</a> to make it dirt simple to pack and push new NuGet packages.</p>

<p>Here’s a list of some of the script’s features:</p>

<ul>
  <li>Create the .nupkg package file and optionally push the package to the NuGet Gallery (or a custom gallery).</li>
  <li>Can be ran from Windows Explorer (i.e. double-click it) or called via PowerShell if you want to be able to pass in specific parameters or suppress prompts.</li>
  <li>Can prompt user for version number and release notes (prompts are prefilled with previous version number and release notes) or can suppress all prompts.</li>
</ul>

<p>This makes packing and pushing your NuGet packages quick and easy, whether doing it manually or integrating it into your build system. Creating NuGet packages wasn’t overly complicated before, but this makes it even simpler and less tedious.</p>

<p><a href="https://newnugetpackage.codeplex.com/">Go to the codeplex page</a> to download the script and start automating your NuGet package creating today. The <a href="https://newnugetpackage.codeplex.com/documentation">codeplex documentation</a> describes the script in much more detail, as well as step by step instructions on how to get setup to start using it.</p>

<p>[UPDATE] I have also used this script in a new NuGet package that will automatically create a NuGet package for your own projects without you having to do anything. <a href="https://blog.danskingdom.com/automatically-create-your-projects-nuget-package-every-time-it-builds-via-nuget/">Read about it here</a>. [/UPDATE]</p>

<h2 id="additional-nuget-information">Additional NuGet Information</h2>

<p>During my investigation I compiled a list of what happens when doing “nuget spec” and “nuget pack” against the various different file types (e.g. dll vs. project vs. nuspec). Someone else may find this information useful, so here it is:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Spec a Project or DLL directly (e.g. "nuget spec PathToFile"):
- Creates a partial .nuspec; still has placeholder info for some fields (e.g. Id, Dependencies).
- Creates [full file name with extension].nuspec file.
- The generated .nuspec file is meant to still be manually updated before making a package from it.

// TestProject.csproj.nuspec
<span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;package</span> <span class="nt">&gt;</span>
  <span class="nt">&lt;metadata&gt;</span>
    <span class="nt">&lt;id&gt;</span>C:\dev\TFS\RQ\Dev\Tools\DevOps\New-NuGetPackage\TestProject\TestProject\TestProject.csproj<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0.0<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;authors&gt;</span>Dan Schroeder<span class="nt">&lt;/authors&gt;</span>
    <span class="nt">&lt;owners&gt;</span>Dan Schroeder<span class="nt">&lt;/owners&gt;</span>
    <span class="nt">&lt;licenseUrl&gt;</span>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/licenseUrl&gt;</span>
    <span class="nt">&lt;projectUrl&gt;</span>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/projectUrl&gt;</span>
    <span class="nt">&lt;iconUrl&gt;</span>http://ICON_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/iconUrl&gt;</span>
    <span class="nt">&lt;requireLicenseAcceptance&gt;</span>false<span class="nt">&lt;/requireLicenseAcceptance&gt;</span>
    <span class="nt">&lt;description&gt;</span>Package description<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;releaseNotes&gt;</span>Summary of changes made in this release of the package.<span class="nt">&lt;/releaseNotes&gt;</span>
    <span class="nt">&lt;copyright&gt;</span>Copyright 2013<span class="nt">&lt;/copyright&gt;</span>
    <span class="nt">&lt;tags&gt;</span>Tag1 Tag2<span class="nt">&lt;/tags&gt;</span>
    <span class="nt">&lt;dependencies&gt;</span>
      <span class="nt">&lt;dependency</span> <span class="na">id=</span><span class="s">"SampleDependency"</span> <span class="na">version=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>
  <span class="nt">&lt;/metadata&gt;</span>
<span class="nt">&lt;/package&gt;</span>
=====================================================================
Spec a DLL using "nuget spec" from the same directory:
- Creates a partial .nuspec; still has placeholder info for some fields (e.g. Id, Dependencies).
- Creates "Package.nuspec" file.
- The generated .nuspec file is meant to still be manually updated before making a package from it.

// Package.nuspec
<span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;package</span> <span class="nt">&gt;</span>
  <span class="nt">&lt;metadata&gt;</span>
    <span class="nt">&lt;id&gt;</span>Package<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0.0<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;authors&gt;</span>Dan Schroeder<span class="nt">&lt;/authors&gt;</span>
    <span class="nt">&lt;owners&gt;</span>Dan Schroeder<span class="nt">&lt;/owners&gt;</span>
    <span class="nt">&lt;licenseUrl&gt;</span>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/licenseUrl&gt;</span>
    <span class="nt">&lt;projectUrl&gt;</span>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/projectUrl&gt;</span>
    <span class="nt">&lt;iconUrl&gt;</span>http://ICON_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/iconUrl&gt;</span>
    <span class="nt">&lt;requireLicenseAcceptance&gt;</span>false<span class="nt">&lt;/requireLicenseAcceptance&gt;</span>
    <span class="nt">&lt;description&gt;</span>Package description<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;releaseNotes&gt;</span>Summary of changes made in this release of the package.<span class="nt">&lt;/releaseNotes&gt;</span>
    <span class="nt">&lt;copyright&gt;</span>Copyright 2013<span class="nt">&lt;/copyright&gt;</span>
    <span class="nt">&lt;tags&gt;</span>Tag1 Tag2<span class="nt">&lt;/tags&gt;</span>
    <span class="nt">&lt;dependencies&gt;</span>
      <span class="nt">&lt;dependency</span> <span class="na">id=</span><span class="s">"SampleDependency"</span> <span class="na">version=</span><span class="s">"1.0"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>
  <span class="nt">&lt;/metadata&gt;</span>
<span class="nt">&lt;/package&gt;</span>
=====================================================================
Spec a Project using "nuget spec" from the same directory:
- Creates a template .nuspec using the proper properties and dependencies pulled from the file.
- Creates [file name without extension].nuspec file.
- The generated .nuspec file can be used to pack with, assuming you are packing the Project and not the .nuspec directly.

// TestProject.nuspec
<span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;package</span> <span class="nt">&gt;</span>
  <span class="nt">&lt;metadata&gt;</span>
    <span class="nt">&lt;id&gt;</span>$id$<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;version&gt;</span>$version$<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;title&gt;</span>$title$<span class="nt">&lt;/title&gt;</span>
    <span class="nt">&lt;authors&gt;</span>$author$<span class="nt">&lt;/authors&gt;</span>
    <span class="nt">&lt;owners&gt;</span>$author$<span class="nt">&lt;/owners&gt;</span>
    <span class="nt">&lt;licenseUrl&gt;</span>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/licenseUrl&gt;</span>
    <span class="nt">&lt;projectUrl&gt;</span>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/projectUrl&gt;</span>
    <span class="nt">&lt;iconUrl&gt;</span>http://ICON_URL_HERE_OR_DELETE_THIS_LINE<span class="nt">&lt;/iconUrl&gt;</span>
    <span class="nt">&lt;requireLicenseAcceptance&gt;</span>false<span class="nt">&lt;/requireLicenseAcceptance&gt;</span>
    <span class="nt">&lt;description&gt;</span>$description$<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;releaseNotes&gt;</span>Summary of changes made in this release of the package.<span class="nt">&lt;/releaseNotes&gt;</span>
    <span class="nt">&lt;copyright&gt;</span>Copyright 2013<span class="nt">&lt;/copyright&gt;</span>
    <span class="nt">&lt;tags&gt;</span>Tag1 Tag2<span class="nt">&lt;/tags&gt;</span>
  <span class="nt">&lt;/metadata&gt;</span>
<span class="nt">&lt;/package&gt;</span>
=====================================================================
Pack a Project (without accompanying template .nuspec):
- Does not generate a .nuspec file; just creates the .nupkg file with proper properties and dependencies pulled from project file.
- Throws warnings about any missing data in the project file (e.g. Description, Author), but still generates the package.

=====================================================================
Pack a Project (with accompanying template .nuspec):
- Expects the [file name without extension].nuspec file to exist in same directory as project file, otherwise it doesn't use a .nuspec file for the packing.
- Throws errors about any missing data in the project file if the .nuspec uses tokens (e.g. $description$, $author$) and these aren't defined in the project, so the package is not generated.

=====================================================================
Cannot pack a .dll directly

=====================================================================
Pack a .nuspec:
- Creates the .nupkg file with properties and dependencies defined in .nuspec file.
- .nuspec file cannot have any placeholder values (e.g. $id$, $version$).
</code></pre></div></div>]]></content><author><name>Daniel Schroeder</name></author><category term="NuGet" /><category term="PowerShell" /><category term="assembly" /><category term="Create" /><category term="dll" /><category term="New" /><category term="NuGet" /><category term="pack" /><category term="Package" /><category term="PowerShell" /><category term="project" /><category term="script" /><category term="spec" /><summary type="html"><![CDATA[I’ve spent a good chunk of time investigating how NuGet.exe works and creating a PowerShell script called New-NuGetPackage to make it dirt simple to pack and push new NuGet packages.]]></summary></entry><entry><title type="html">Fix Problem Where Windows PowerShell Cannot Run Script Whose Path Contains Spaces</title><link href="https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces/" rel="alternate" type="text/html" title="Fix Problem Where Windows PowerShell Cannot Run Script Whose Path Contains Spaces" /><published>2013-05-28T18:58:25+00:00</published><updated>2013-05-28T18:58:25+00:00</updated><id>https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces</id><content type="html" xml:base="https://blog.danskingdom.com/fix-problem-where-windows-powershell-cannot-run-script-whose-path-contains-spaces/"><![CDATA[<p>Most people will likely find the “Run script path with spaces from File Explorer” (to be able to double click a PS script whose path contains spaces to run it) section below the most helpful. Most of the other content in this post can be found elsewhere, but I provide it for context and completeness.</p>

<h2 id="make-running-instead-of-editing-the-default-powershell-script-action">Make running (instead of editing) the default PowerShell script action</h2>

<p>The default Windows action when you double click on a PowerShell script is to open it in an editor, rather than to actually run the script. If this bugs you, it’s easy enough to fix. Just right-click on the script, go to “Open with” –&gt; “Choose default program…”, and then select Windows PowerShell, making sure the “Use this app for all .ps1 files” option is checked (this might be called “Always use the selected program to open this kind of file” or something else depending on which version of Windows you are using).</p>

<p><img src="/assets/Posts/2013/05/ChooseDefaultPowerShellApplication.png" alt="Choose Default PowerShell Application" /> <img src="/assets/Posts/2013/05/MakeWindowsPowerShellDefaultApplication.png" alt="Make Windows PowerShell Default Application" /></p>

<p>If you don’t mind opening in an editor as the default action, then to run the script you can just right-click on the script and choose “Open with” –&gt; “Windows PowerShell”. This is probably how 90% of people run their PowerShell scripts; power uses might run their scripts directly from the PowerShell command prompt.</p>

<h2 id="error-message-when-trying-to-run-a-script-whose-path-contains-spaces">Error message when trying to run a script whose path contains spaces</h2>

<p>So the problem that the 90% of people are likely to encounter is that as soon as the script path has a space in it (either in the filename itself or in the directory path the file resides in), they will see the powershell console flash some red text at them for about 1/10th of a second before it closes, and they will be wondering why the script did not run; or worse, they won’t know that it didn’t run (see the “Keep PowerShell Console Open” section below). If they are lucky enough to press Print Screen at the right moment, or decide to open up a PowerShell console and run from there, they might see an error message similar to this:</p>

<p><img src="/assets/Posts/2013/05/Powershell-Invalid-Path-Error-Message.png" alt="Powershell Invalid Path Error Message" /></p>

<blockquote>
  <p>The term ‘C:\My’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.</p>
</blockquote>

<p>So the path to the script I was trying to run is “C:\My Folder\My PowerShell Script.ps1”, but from the error you can see that it cut the path off at the first space.</p>

<h2 id="run-script-path-with-spaces-from-powershell-console">Run script path with spaces from PowerShell console</h2>

<p>So the typical work around for this is to open a PowerShell console and run the script by enclosing the path in double quotes.</p>

<blockquote>
  <p><strong>Windows 8 Pro Tip:</strong> You can open the PowerShell console at your current directory in File Explorer by choosing File –&gt; Open Windows PowerShell.</p>
</blockquote>

<p><img src="/assets/Posts/2013/05/Open-Powershell-From-File-Explorer.png" alt="Open Powershell From File Explorer" /></p>

<p>If you simply try to run the script by enclosing the path to the script in double quotes you will just see the path spit back at you in the console, instead of actually running the script.</p>

<p><img src="/assets/Posts/2013/05/Try-to-run-script-with-spaces-the-wrong-way.png" alt="Try to run script with spaces the wrong way" /></p>

<p>The trick is that you have to put “&amp; “ before the script path to actually run the script. Also, if you are trying to run a script from the current directory without using the full path, you will need to put “." before the relative script filename.</p>

<p><img src="/assets/Posts/2013/05/Run-PowerShell-script-the-right-way.png" alt="Run PowerShell script the right way" /></p>

<h2 id="run-script-path-with-spaces-from-file-explorer">Run script path with spaces from File Explorer</h2>

<p>So when we are in the PowerShell console we can manually type the path enclosed in double quotes, but what do we do when simply trying to run the file from File Explorer (i.e. Windows Explorer in Windows 7 and previous) by double clicking it?</p>

<p><strong>The answer:</strong> Edit the registry to pass the file path to powershell.exe with the path enclosed in quotes.</p>

<p>The problem is that the “HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command” registry key value looks like this:</p>

<blockquote>
  <p>“C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe” “%1”</p>
</blockquote>

<p>but we want it to look like this:</p>

<blockquote>
  <p>“C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe” “&amp; "%1"”</p>
</blockquote>

<p>So if you want to go manually edit that key by hand you can, or you can simply download the registry script below and then double click the .reg file to have it update the registry key value for you (choose Yes when asked if you want to continue).</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command]
@="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" \"&amp; \\\"%1\\\"\""
</code></pre></div></div>

<p><a href="/assets/Posts/2013/09/FixRunPowerShellScriptWithSpacesInPathProblem.zip">Download Registry Script</a></p>

<p>IMHO this seems like a bug with the PowerShell installer (and Windows since PowerShell is built into Windows 7 and up), so please go <a href="https://connect.microsoft.com/PowerShell/feedback/details/788806/powershell-script-cannot-be-ran-outside-of-console-if-path-contains-spaces">up-vote the bug I submitted to get this fixed</a>.</p>

<p>So now you can run your PowerShell scripts from File Explorer regardless of whether their path contains spaces or not :-). For those interested, <a href="http://superuser.com/questions/445925/how-to-add-item-to-right-click-menu-when-not-selecting-a-folder-or-file">this is the post</a> that got me thinking about using the registry to fix this problem.</p>

<h2 id="bonus-keep-powershell-console-open-when-script-is-ran-from-file-explorer">Bonus: Keep PowerShell console open when script is ran from File Explorer</h2>

<p><strong>Update</strong> - This Bonus section now has its own <a href="https://blog.danskingdom.com/keep-powershell-console-window-open-after-script-finishes-running/">updated dedicated post here</a> that you should use instead.</p>

<p>When running a script by double-clicking it, if the script completes very quickly the user will see the PowerShell console appear very briefly and then disappear. If the script gives output that the user wants to see, or if it throws an error, the user won’t have time to read the text. The typical work around is to open the PowerShell console and manually run the script. The other option is to adjust our new registry key value a bit.</p>

<p>So to keep the PowerShell console window open after the script completes, we just need to change our new key value to use the <code class="language-plaintext highlighter-rouge">–NoExit</code> switch:</p>

<blockquote>
  <p>“C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe” –NoExit “&amp; "%1"”</p>
</blockquote>

<p>And here is the .reg script with the –NoExit switch included:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Applications\powershell.exe\shell\open\command]
@="\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -NoExit \"&amp; \\\"%1\\\"\""
</code></pre></div></div>

<p><a href="/assets/Posts/2013/09/FixRunPowerShellScriptWithSpacesInPathProblemAndLeaveConsoleOpenWhenScriptCompletes.zip">Download Registry Script</a></p>

<p>I hope you find this information as useful as I did. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="Console" /><category term="Context Menu" /><category term="Default" /><category term="Double Click" /><category term="error" /><category term="Execute" /><category term="Explorer" /><category term="File Explorer" /><category term="Open With" /><category term="Path" /><category term="PowerShell" /><category term="Registry" /><category term="Right Click" /><category term="script" /><category term="Space" /><category term="Spaces" /><category term="Windows Explorer" /><category term="Windows PowerShell" /><summary type="html"><![CDATA[Most people will likely find the “Run script path with spaces from File Explorer” (to be able to double click a PS script whose path contains spaces to run it) section below the most helpful. Most of the other content in this post can be found elsewhere, but I provide it for context and completeness.]]></summary></entry><entry><title type="html">Why I Chose WordPress Over Geeks With Blogs, And Moving From WordPress.com To A GoDaddy Hosted Solution</title><link href="https://blog.danskingdom.com/why-i-chose-wordpress-over-geeks-with-blogs-and-moving-from-wordpress-com-to-a-godaddy-hosted-solution/" rel="alternate" type="text/html" title="Why I Chose WordPress Over Geeks With Blogs, And Moving From WordPress.com To A GoDaddy Hosted Solution" /><published>2013-05-17T23:56:25+00:00</published><updated>2013-05-17T23:56:25+00:00</updated><id>https://blog.danskingdom.com/why-i-chose-wordpress-over-geeks-with-blogs-and-moving-from-wordpress-com-to-a-godaddy-hosted-solution</id><content type="html" xml:base="https://blog.danskingdom.com/why-i-chose-wordpress-over-geeks-with-blogs-and-moving-from-wordpress-com-to-a-godaddy-hosted-solution/"><![CDATA[<p>A while back I wrote about <a href="https://blog.danskingdom.com/migrating-my-gwb-blog-over-to-wordpress/">some reasons why I didn’t like GWB (Geeks With Blogs) and was attracted to WordPress</a>. 6 months later and I am confident that I made the right decision. GWB was good to me, but their UI and features are just too dated and can’t keep up with what WordPress has to offer. They said that they had hired some developers to improve the site, but it’s been 6 months and I can’t really see a difference. When I wrote my last post the one thing that I didn’t like about WordPress.com was that I couldn’t find a theme that would stretch the content area to the full screen width; which is pretty essential for me since a lot of my posts involve long code snippets. I never did find a theme to do this on WordPress.com (although I gave up after a few hours or searching). Instead I decided to move to hosting my own WordPress instance.</p>

<p>I have a <a href="http://www.danskingdom.com">few</a> <a href="http://www.xnaparticles.com">other</a> <a href="http://www.helpfulpctools.com">websites</a> that I was hosting on my own personal server at home, but decided to move to <a href="http://www.godaddy.com">GoDaddy</a> for my hosting since they had a half price special and it would cost me less than my power bill to keep my server running (and I already had my domains registered through them). I opted for the Deluxe package since I have a few websites, and GoDaddy offers up to 25 free WordPress installations with this package, so I have moved my blog from my deadlydog.wordpress.com site to my GoDaddy-hosted blogs.danskingdom.com site. The migration process should have been painless; just export my deadlydog.wordpress.com site to an xml file (this part was very easy and painless), and then import it into my new blogs.danskingdom.com site. For some reason when trying to do the import I would often get a “Connection was reset by the server” error. So it took me probably about 15 tries before it actually imported everything properly; a few times it died half way through the import, but most of the time it would die before the import even started. Luckily blowing away and recreating an WordPress instance with GoDaddy is easy and only takes about 30 minutes. Another reason I chose GoDaddy were that their base prices are pretty competitive, plus I consistently receive coupon codes in my email from them for an additional 25% or 30% off (hint, use referral code <strong>WOWdeadlyd</strong> to get 35% off orders on new products). Also, their customer service is wonderful. The support I’ve received via email has been only ok, but anytime I’ve actually called in to their support they’ve been awesome and very helpful.</p>

<p>So once I finally got all of my posts migrated to my own hosted instance I could fix some of the things that annoyed me about my wordpress.com site. First, I’m able to modify the theme’s css directly without paying anything. This allowed me to go in and stretch the content area to fit the user’s screen. You can probably tell while reading this that it isn’t perfect; there is some extra space on the right-hand side, but meh, it’s much better than it was. Second, it allowed me to add advertisements to my site, which help pay for the hosting costs. You might be thinking, “What? I don’t see any ads”, but that’s because I’ve placed them at the very bottom of the page to be as non-obtrusive as possible. Third, it allowed me to hook up <a href="http://www.google.ca/analytics/">Google Analytics</a> to my site so that I can get even more information about my site’s traffic and visitors, and see what search terms were bringing people to my site. And by hosting my own WordPress instance I can now install any WordPress plugin that I want, instead of only the ones that WordPress.com has allowed. One thing that I’ve noticed however is that accessing and navigating links on my GoDaddy hosted WordPress site is often slower than it was on WordPress.com. I’m not sure if this is a temporary thing or what (since I’ve only been hosting with GoDaddy for about a week), but I often notice myself waiting for a webpage to load now, where I never noticed this before with deadlydog.wordpress.com. However sometimes a page is super quick to load, so it’s not consistent.</p>

<p>One thing that does sort of suck about moving from WordPress.com to my own hosted solution is that I couldn’t figure out how to transfer my site stats across, so my stats for blog.danskingdom.com only start from the day I setup the new blog on that domain. Another thing that I didn’t realize until after I had setup my new WordPress account was that I would have to pay to have traffic from my old deadlydog.wordpress.com redirected to the new blog.danskingdom.com; it was only $13 for a year though, so that’s not too bad as I’ll likely delete my old wordpress.com site right before that expires.</p>

<p>One other change I had to make when moving from WordPress.com to my own hosted solution was that in order for my code snippets to show up properly I had to install <a href="http://wordpress.org/extend/plugins/syntaxhighlighter/">SyntaxHighlighter Evolved</a> on my WordPress instance, which was super simple to do, and I can still use the <a href="http://richhewlett.com/wlwsourcecodeplugin/">Source Code Highlighter Plugin for WordPress.com plugin for Windows Live Writer</a> which is great.</p>

<p>GWB users may have noticed that I still post to GWB with a “Read more at” link that points to the post on my new domain (in fact I went and updated all of my old posts to “read more” at my new domain. GWB doesn’t offer any way to forward my blog with them to a different domain (not even a paid solution like WordPress.com), so updating my old posts with a link to my new domain was the best I could do). This is because the one and only thing that I’ll miss about GWB is the community. While double posting (bad I know, but at least I’ve switched to the “Read more at” method rather than double posting the entire post) I found that I would still get more comments on my GWB posts than my WordPress posts. Did this mean that my GWB posts were getting more traffic though? I can’t tell because GWB doesn’t offer any sort of information or stats about how many visits my site receives, and I can’t hook up Google Analytics or other 3rd party services to it either. I’m going to continue double posting with the “Read more at” links so that GWB users can still see my post titles on their feeds and don’t miss out on anything I post that they might find useful, as I too often monitor the GWB main page feed to see other code-related posts that I don’t want to miss out on. Also, by changing to using the “Read more at” convention it allows me to only have to update my one website when I update an old blog post, rather than having to update multiple; this was a major pain point, especially when I needed to use different Windows Live Writer (WLW) plugins depending on which site I was posting code to.</p>

<p>Anyways, these are my thoughts on my blog’s move. Hopefully it helps you with making a decision about going with GWB or maintaining your own hosted WordPress solution, and whether you choose GoDaddy or not. Again, use referral code <strong>WOWdeadlyd</strong> to get 35% off your GoDaddy order.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows Live Writer" /><category term="WordPress" /><category term="Geeks With Blogs" /><category term="GoDaddy" /><category term="GWB" /><category term="WordPress" /><summary type="html"><![CDATA[A while back I wrote about some reasons why I didn’t like GWB (Geeks With Blogs) and was attracted to WordPress. 6 months later and I am confident that I made the right decision. GWB was good to me, but their UI and features are just too dated and can’t keep up with what WordPress has to offer. They said that they had hired some developers to improve the site, but it’s been 6 months and I can’t really see a difference. When I wrote my last post the one thing that I didn’t like about WordPress.com was that I couldn’t find a theme that would stretch the content area to the full screen width; which is pretty essential for me since a lot of my posts involve long code snippets. I never did find a theme to do this on WordPress.com (although I gave up after a few hours or searching). Instead I decided to move to hosting my own WordPress instance.]]></summary></entry><entry><title type="html">Powershell functions to get an xml node, and get and set an xml element’s value, even when the element does not already exist</title><link href="https://blog.danskingdom.com/powershell-functions-to-get-an-xml-node-and-get-and-set-an-xml-elements-value-even-when-the-element-does-not-already-exist/" rel="alternate" type="text/html" title="Powershell functions to get an xml node, and get and set an xml element’s value, even when the element does not already exist" /><published>2013-05-16T23:16:57+00:00</published><updated>2017-01-07T00:00:00+00:00</updated><id>https://blog.danskingdom.com/powershell-functions-to-get-an-xml-node-and-get-and-set-an-xml-elements-value-even-when-the-element-does-not-already-exist</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-functions-to-get-an-xml-node-and-get-and-set-an-xml-elements-value-even-when-the-element-does-not-already-exist/"><![CDATA[<p>I’m new to working with Xml through PowerShell and was so impressed when I discovered how easy it was to read an xml element’s value. I’m working with reading/writing .nuspec files for working with NuGet. Here’s a sample xml of a .nuspec xml file:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span>
<span class="nt">&lt;package</span> <span class="na">xmlns=</span><span class="s">"http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;metadata&gt;</span>
    <span class="nt">&lt;id&gt;</span>MyAppsId<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0.1<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;title&gt;</span>MyApp<span class="nt">&lt;/title&gt;</span>
    <span class="nt">&lt;authors&gt;</span>Daniel Schroeder<span class="nt">&lt;/authors&gt;</span>
    <span class="nt">&lt;owners&gt;</span>Daniel Schroeder<span class="nt">&lt;/owners&gt;</span>
    <span class="nt">&lt;requireLicenseAcceptance&gt;</span>false<span class="nt">&lt;/requireLicenseAcceptance&gt;</span>
    <span class="nt">&lt;description&gt;</span>My App.<span class="nt">&lt;/description&gt;</span>
    <span class="nt">&lt;summary&gt;</span>My App.<span class="nt">&lt;/summary&gt;</span>
    <span class="nt">&lt;tags&gt;</span>Powershell, Application<span class="nt">&lt;/tags&gt;</span>
  <span class="nt">&lt;/metadata&gt;</span>
  <span class="nt">&lt;files&gt;</span>
    <span class="nt">&lt;file</span> <span class="na">src=</span><span class="s">"MyApp.ps1"</span> <span class="na">target=</span><span class="s">"content\MyApp.ps1"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/files&gt;</span>
<span class="nt">&lt;/package&gt;</span>
</code></pre></div></div>

<p>In PowerShell if I want to get the version element’s value, I can just do:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Read in the file contents and return the version node's value.</span><span class="w">
</span><span class="p">[</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$NuSpecFilePath</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="nv">$fileContents</span><span class="o">.</span><span class="nf">package</span><span class="o">.</span><span class="nf">metadata</span><span class="o">.</span><span class="nf">version</span><span class="w">
</span></code></pre></div></div>

<p>Wow, that’s super easy. And if I want to update that version number, I can just do:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Read in the file contents, update the version node's value, and save the file.</span><span class="w">
</span><span class="p">[</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$NuSpecFilePath</span><span class="w">
</span><span class="nv">$fileContents</span><span class="o">.</span><span class="nf">package</span><span class="o">.</span><span class="nf">metadata</span><span class="o">.</span><span class="nf">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$NewVersionNumber</span><span class="w">
</span><span class="nv">$fileContents</span><span class="o">.</span><span class="nf">Save</span><span class="p">(</span><span class="nv">$NuSpecFilePath</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>Holy smokes. So simple it blows my mind. So everything is great, right? Well, it is until you try and read or write to an element that doesn’t exist. If the <code class="language-plaintext highlighter-rouge">&lt;version&gt;</code> element is not in the xml, when I try and read from it or write to it, I get an error such as “Error: Property ‘version’ cannot be found on this object. Make sure that it exists.”. You would think that checking if an element exists would be straight-forward and easy right? Well, it almost is. There’s a <a href="http://msdn.microsoft.com/en-us/library/system.xml.xmlnode.selectsinglenode.aspx">SelectSingleNode() function</a> that we can use to look for the element, but what I realized after a couple hours of banging my head on the wall and <a href="http://stackoverflow.com/questions/1766254/selectsinglenode-always-returns-null">stumbling across this stack overflow post</a>, is that in order for this function to work properly, you really need to use the overloaded method that also takes an XmlNamespaceManager; otherwise the SelectSingleNode() function always returns null.</p>

<p>So basically you need an extra 2 lines in order to setup an XmlNamespaceManager every time you need to look for a node. This is a little painful, so instead I created this function that will get you the node if it exists, and return $null if it doesn’t:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlNode</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># If a Namespace URI was not given, use the Xml document's default namespace.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$NamespaceURI</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">DocumentElement</span><span class="o">.</span><span class="nf">NamespaceURI</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up.</span><span class="w">
    </span><span class="nv">$xmlNsManager</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Xml.XmlNamespaceManager</span><span class="p">(</span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">NameTable</span><span class="p">)</span><span class="w">
    </span><span class="nv">$xmlNsManager</span><span class="o">.</span><span class="nf">AddNamespace</span><span class="p">(</span><span class="s2">"ns"</span><span class="p">,</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="p">)</span><span class="w">
    </span><span class="nv">$fullyQualifiedNodePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/ns:</span><span class="si">$(</span><span class="nv">$NodePath</span><span class="o">.</span><span class="nf">Replace</span><span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="nv">$NodeSeparatorCharacter</span><span class="si">)</span><span class="s2">, '/ns:'))"</span><span class="w">

    </span><span class="c"># Try and get the node, then return it. Returns $null if the node was not found.</span><span class="w">
    </span><span class="nv">$node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">SelectSingleNode</span><span class="p">(</span><span class="nv">$fullyQualifiedNodePath</span><span class="p">,</span><span class="w"> </span><span class="nv">$xmlNsManager</span><span class="p">)</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$node</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>And you would call this function like so:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Read in the file contents and return the version node's value.</span><span class="w">
</span><span class="p">[</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$NuSpecFilePath</span><span class="w">
</span><span class="nv">$node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNode</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$fileContents</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="s2">"package.metadata.version"</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$node</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="nv">$fileContents</span><span class="o">.</span><span class="nf">package</span><span class="o">.</span><span class="nf">metadata</span><span class="o">.</span><span class="nf">version</span><span class="w">
</span></code></pre></div></div>

<p>So if the node doesn’t exist (i.e. is $null), I return $null instead of trying to access the non-existent element.</p>

<p>So by default this Get-XmlNode function uses the xml’s root namespace, which is what we want 95% of the time. It also takes a NodeSeparatorCharacter that defaults to a period. While Googling for answers I saw that many people use the the syntax “$fileContents/package/metadata/version” instead of “$fileContents.package.metadata.version”. I prefer the dot notation, but for those who like the slash just override the NodeSeparatorCharacter with a slash.</p>

<hr />

<h3 id="update-1">Update 1</h3>

<p>Later I found that I also wanted the ability to return back multiple xml nodes; that is, if multiple “version” elements were defined I wanted to get them all, not just the first one. This is simple; instead of using .SelectSingleNode() we can use .SelectNodes(). In order to avoid duplicating code, I broke the code to get the Xml Namespace Manager and Fully Qualified Node Path out into their own functions. Here is the rewritten code, with the new Get-XmlNodes function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlNamespaceManager</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># If a Namespace URI was not given, use the Xml document's default namespace.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$NamespaceURI</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">DocumentElement</span><span class="o">.</span><span class="nf">NamespaceURI</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># In order for SelectSingleNode() to actually work, we need to use the fully qualified node path along with an Xml Namespace Manager, so set them up.</span><span class="w">
    </span><span class="p">[</span><span class="n">System.Xml.XmlNamespaceManager</span><span class="p">]</span><span class="nv">$xmlNsManager</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Xml.XmlNamespaceManager</span><span class="p">(</span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">NameTable</span><span class="p">)</span><span class="w">
    </span><span class="nv">$xmlNsManager</span><span class="o">.</span><span class="nf">AddNamespace</span><span class="p">(</span><span class="s2">"ns"</span><span class="p">,</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="p">)</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">,</span><span class="nv">$xmlNsManager</span><span class="w">   </span><span class="c"># Need to put the comma before the variable name so that PowerShell doesn't convert it into an Object[].</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-FullyQualifiedXmlNodePath</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="s2">"/ns:</span><span class="si">$(</span><span class="nv">$NodePath</span><span class="o">.</span><span class="nf">Replace</span><span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="nv">$NodeSeparatorCharacter</span><span class="si">)</span><span class="s2">, '/ns:'))"</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlNode</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$xmlNsManager</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNamespaceManager</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$fullyQualifiedNodePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-FullyQualifiedXmlNodePath</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$NodePath</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

    </span><span class="c"># Try and get the node, then return it. Returns $null if the node was not found.</span><span class="w">
    </span><span class="nv">$node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">SelectSingleNode</span><span class="p">(</span><span class="nv">$fullyQualifiedNodePath</span><span class="p">,</span><span class="w"> </span><span class="nv">$xmlNsManager</span><span class="p">)</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$node</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlNodes</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$xmlNsManager</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNamespaceManager</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w">
    </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$fullyQualifiedNodePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-FullyQualifiedXmlNodePath</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$NodePath</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

    </span><span class="c"># Try and get the nodes, then return them. Returns $null if no nodes were found.</span><span class="w">
    </span><span class="nv">$nodes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">SelectNodes</span><span class="p">(</span><span class="nv">$fullyQualifiedNodePath</span><span class="p">,</span><span class="w"> </span><span class="nv">$xmlNsManager</span><span class="p">)</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$nodes</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Note the comma in the return statement of the Get-XmlNamespaceManager function. It took me a while to discover <a href="http://stackoverflow.com/questions/17498320/powershell-changes-return-objects-type">why things broke without it</a>.</p>

<hr />

<p>So once I had this, I decided that I might as well make functions for easily getting and setting the text values of an xml element, which is what is provided here:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlElementsTextValue</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ElementPath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Try and get the node.</span><span class="w">
    </span><span class="nv">$node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNode</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

    </span><span class="c"># If the node already exists, return its value, otherwise return null.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$node</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="nv">$node</span><span class="o">.</span><span class="nf">InnerText</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Set-XmlElementsTextValue</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ElementPath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$TextValue</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Try and get the node.</span><span class="w">
    </span><span class="nv">$node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNode</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

    </span><span class="c"># If the node already exists, update its value.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$node</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$node</span><span class="o">.</span><span class="nf">InnerText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$TextValue</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="c"># Else the node doesn't exist yet, so create it with the given value.</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># Create the new element with the given value.</span><span class="w">
        </span><span class="nv">$elementName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">SubString</span><span class="p">(</span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">LastIndexOf</span><span class="p">(</span><span class="nv">$NodeSeparatorCharacter</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
         </span><span class="nv">$element</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">CreateElement</span><span class="p">(</span><span class="nv">$elementName</span><span class="p">,</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">DocumentElement</span><span class="o">.</span><span class="nf">NamespaceURI</span><span class="p">)</span><span class="w">
        </span><span class="nv">$textNode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">CreateTextNode</span><span class="p">(</span><span class="nv">$TextValue</span><span class="p">)</span><span class="w">
        </span><span class="nv">$element</span><span class="o">.</span><span class="nf">AppendChild</span><span class="p">(</span><span class="nv">$textNode</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">

        </span><span class="c"># Try and get the parent node.</span><span class="w">
        </span><span class="nv">$parentNodePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">SubString</span><span class="p">(</span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">LastIndexOf</span><span class="p">(</span><span class="nv">$NodeSeparatorCharacter</span><span class="p">))</span><span class="w">
        </span><span class="nv">$parentNode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNode</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$parentNodePath</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$parentNode</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$parentNode</span><span class="o">.</span><span class="nf">AppendChild</span><span class="p">(</span><span class="nv">$element</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"</span><span class="nv">$parentNodePath</span><span class="s2"> does not exist in the xml."</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The Get-XmlElementsTextValue function is pretty straight forward; return the value if it exists, otherwise return null. The Set-XmlElementsTextValue is a little more involved because if the element does not exist already, we need to create the new element and attach it as a child to the parent element.</p>

<p>Here’s an example of calling Get-XmlElementsTextValue:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Read in the file contents and return the version element's value.</span><span class="w">
</span><span class="p">[</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$NuSpecFilePath</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="n">Get-XmlElementsTextValue</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$fileContents</span><span class="w"> </span><span class="nt">-ElementPath</span><span class="w"> </span><span class="s2">"package.metadata.version"</span><span class="w">
</span></code></pre></div></div>

<p>And an example of calling Set-XmlElementsTextValue:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Read in the file contents, update the version element's value, and save the file.</span><span class="w">
</span><span class="p">[</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$fileContents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$NuSpecFilePath</span><span class="w">
</span><span class="n">Set-XmlElementsTextValue</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$fileContents</span><span class="w"> </span><span class="nt">-ElementPath</span><span class="w"> </span><span class="s2">"package.metadata.version"</span><span class="w"> </span><span class="nt">-TextValue</span><span class="w"> </span><span class="nv">$NewVersionNumber</span><span class="w">
</span><span class="nv">$fileContents</span><span class="o">.</span><span class="nf">Save</span><span class="p">(</span><span class="nv">$NuSpecFilePath</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>Note that these 2 functions depend on the Get-XmlNode function provided above.</p>

<hr />

<h3 id="update-2---january-7-2016">Update 2 - January 7, 2016</h3>

<p>I have had multiple people ask me for similar functions for getting and setting an element’s Attribute value as well, so here are the corresponding functions for that:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-XmlElementsAttributeValue</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ElementPath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$AttributeName</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Try and get the node.</span><span class="w">
    </span><span class="nv">$node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNode</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

    </span><span class="c"># If the node and attribute already exist, return the attribute's value, otherwise return null.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$node</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$node</span><span class="o">.</span><span class="nv">$AttributeName</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="nv">$node</span><span class="o">.</span><span class="nv">$AttributeName</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Set-XmlElementsAttributeValue</span><span class="p">([</span><span class="w"> </span><span class="n">xml</span><span class="w"> </span><span class="p">]</span><span class="nv">$XmlDocument</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ElementPath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$AttributeName</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$AttributeValue</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$NodeSeparatorCharacter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'.'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Try and get the node.</span><span class="w">
    </span><span class="nv">$node</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNode</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

    </span><span class="c"># If the node already exists, create/update its attribute's value.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$node</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nv">$attribute</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">CreateNode</span><span class="p">([</span><span class="n">System.Xml.XmlNodeType</span><span class="p">]::</span><span class="nx">Attribute</span><span class="p">,</span><span class="w"> </span><span class="nv">$AttributeName</span><span class="p">,</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="p">)</span><span class="w">
        </span><span class="nv">$attribute</span><span class="o">.</span><span class="nf">Value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$AttributeValue</span><span class="w">
        </span><span class="nv">$node</span><span class="o">.</span><span class="nf">Attributes</span><span class="o">.</span><span class="nf">SetNamedItem</span><span class="p">(</span><span class="nv">$attribute</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="c"># Else the node doesn't exist yet, so create it with the given attribute value.</span><span class="w">
    </span><span class="kr">else</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># Create the new element with the given value.</span><span class="w">
        </span><span class="nv">$elementName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">SubString</span><span class="p">(</span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">LastIndexOf</span><span class="p">(</span><span class="nv">$NodeSeparatorCharacter</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
        </span><span class="nv">$element</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">CreateElement</span><span class="p">(</span><span class="nv">$elementName</span><span class="p">,</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="o">.</span><span class="nf">DocumentElement</span><span class="o">.</span><span class="nf">NamespaceURI</span><span class="p">)</span><span class="w">
        </span><span class="nv">$element</span><span class="o">.</span><span class="nf">SetAttribute</span><span class="p">(</span><span class="nv">$AttributeName</span><span class="p">,</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="p">,</span><span class="w"> </span><span class="nv">$AttributeValue</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">

        </span><span class="c"># Try and get the parent node.</span><span class="w">
        </span><span class="nv">$parentNodePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">SubString</span><span class="p">(</span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="nv">$ElementPath</span><span class="o">.</span><span class="nf">LastIndexOf</span><span class="p">(</span><span class="nv">$NodeSeparatorCharacter</span><span class="p">))</span><span class="w">
        </span><span class="nv">$parentNode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-XmlNode</span><span class="w"> </span><span class="nt">-XmlDocument</span><span class="w"> </span><span class="nv">$XmlDocument</span><span class="w"> </span><span class="nt">-NodePath</span><span class="w"> </span><span class="nv">$parentNodePath</span><span class="w"> </span><span class="nt">-NamespaceURI</span><span class="w"> </span><span class="nv">$NamespaceURI</span><span class="w"> </span><span class="nt">-NodeSeparatorCharacter</span><span class="w"> </span><span class="nv">$NodeSeparatorCharacter</span><span class="w">

        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$parentNode</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$parentNode</span><span class="o">.</span><span class="nf">AppendChild</span><span class="p">(</span><span class="nv">$element</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"</span><span class="nv">$parentNodePath</span><span class="s2"> does not exist in the xml."</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<hr />

<p>Rather than copy-pasting, you can <a href="/assets/Posts/2014/01/PowerShellFunctionsToGetAndSetXml.zip">download all of the functions shown here</a>.</p>

<p>I hope you find this useful and that it saves you some time. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="XML" /><category term="element" /><category term="exist" /><category term="node" /><category term="null" /><category term="PowerShell" /><category term="SelectNodes" /><category term="SelectSingleNode" /><category term="XML" /><summary type="html"><![CDATA[I’m new to working with Xml through PowerShell and was so impressed when I discovered how easy it was to read an xml element’s value. I’m working with reading/writing .nuspec files for working with NuGet. Here’s a sample xml of a .nuspec xml file:]]></summary></entry><entry><title type="html">PowerShell function to create a password protected zip file</title><link href="https://blog.danskingdom.com/powershell-function-to-create-a-password-protected-zip-file/" rel="alternate" type="text/html" title="PowerShell function to create a password protected zip file" /><published>2013-05-09T23:42:14+00:00</published><updated>2013-05-09T23:42:14+00:00</updated><id>https://blog.danskingdom.com/powershell-function-to-create-a-password-protected-zip-file</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-function-to-create-a-password-protected-zip-file/"><![CDATA[<p>There are <a href="http://stackoverflow.com/questions/1153126/how-to-create-a-zip-archive-with-powershell">a few different ways to create zip files in powershell</a>, but not many that allow you to create one that is password protected. I found <a href="http://community.spiceworks.com/topic/263947-powershell-7-zip-password-protected-zip">this post that shows how to do it using 7zip</a>, so I thought I would share my modified solution.</p>

<p>Here is the function I wrote that uses 7zip to perform the zip, since 7zip supports using a password to zip the files. This script looks for the 7zip executable (7z.exe) in the default install locations, and if not found it will use the stand-alone 7zip executable (7za.exe) if it is in the same directory as the powershell script.</p>

<p><strong>Update</strong>: Updated function to support multiple compression types: 7z, zip, gzip, bzip2, tar, iso, and udf.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Write-ZipUsing7Zip</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$FilesToZip</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ZipOutputFilePath</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$Password</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">ValidateSet</span><span class="p">(</span><span class="s1">'7z'</span><span class="p">,</span><span class="s1">'zip'</span><span class="p">,</span><span class="s1">'gzip'</span><span class="p">,</span><span class="s1">'bzip2'</span><span class="p">,</span><span class="s1">'tar'</span><span class="p">,</span><span class="s1">'iso'</span><span class="p">,</span><span class="s1">'udf'</span><span class="p">)][</span><span class="n">string</span><span class="p">]</span><span class="nv">$CompressionType</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'zip'</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="nv">$HideWindow</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Look for the 7zip executable.</span><span class="w">
    </span><span class="nv">$pathTo32Bit7Zip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Program Files (x86)\7-Zip\7z.exe"</span><span class="w">
    </span><span class="nv">$pathTo64Bit7Zip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"C:\Program Files\7-Zip\7z.exe"</span><span class="w">
    </span><span class="nv">$THIS_SCRIPTS_DIRECTORY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="nv">$</span><span class="nn">script</span><span class="p">:</span><span class="nv">MyInvocation</span><span class="o">.</span><span class="nf">MyCommand</span><span class="o">.</span><span class="nf">Path</span><span class="w">
    </span><span class="nv">$pathToStandAloneExe</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$THIS_SCRIPTS_DIRECTORY</span><span class="w"> </span><span class="s2">"7za.exe"</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$pathTo64Bit7Zip</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$pathTo7ZipExe</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$pathTo64Bit7Zip</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$pathTo32Bit7Zip</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$pathTo7ZipExe</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$pathTo32Bit7Zip</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$pathToStandAloneExe</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$pathTo7ZipExe</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$pathToStandAloneExe</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"Could not find the 7-zip executable."</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># Delete the destination zip file if it already exists (i.e. overwrite it).</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$ZipOutputFilePath</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nv">$ZipOutputFilePath</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="nv">$windowStyle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Normal"</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$HideWindow</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$windowStyle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Hidden"</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># Create the arguments to use to zip up the files.</span><span class="w">
    </span><span class="c"># Command-line argument syntax can be found at: http://www.dotnetperls.com/7-zip-examples</span><span class="w">
    </span><span class="nv">$arguments</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"a -t</span><span class="nv">$CompressionType</span><span class="s2"> ""</span><span class="nv">$ZipOutputFilePath</span><span class="s2">"" ""</span><span class="nv">$FilesToZip</span><span class="s2">"" -mx9"</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$Password</span><span class="p">)))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$arguments</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">" -p</span><span class="nv">$Password</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="c"># Zip up the files.</span><span class="w">
    </span><span class="nv">$p</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Start-Process</span><span class="w"> </span><span class="nv">$pathTo7ZipExe</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="nv">$arguments</span><span class="w"> </span><span class="nt">-Wait</span><span class="w"> </span><span class="nt">-PassThru</span><span class="w"> </span><span class="nt">-WindowStyle</span><span class="w"> </span><span class="nv">$windowStyle</span><span class="w">

    </span><span class="c"># If the files were not zipped successfully.</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">((</span><span class="nv">$p</span><span class="o">.</span><span class="nf">HasExited</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$true</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="nv">$p</span><span class="o">.</span><span class="nf">ExitCode</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"There was a problem creating the zip file '</span><span class="nv">$ZipFilePath</span><span class="s2">'."</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><a href="/assets/Posts/2013/09/Write-ZipUsing7Zip.zip">Download the function script</a></p>

<p>And here’s some examples of how to call the function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Write-ZipUsing7Zip</span><span class="w"> </span><span class="nt">-FilesToZip</span><span class="w"> </span><span class="s2">"C:\SomeFolder"</span><span class="w"> </span><span class="nt">-ZipOutputFilePath</span><span class="w"> </span><span class="s2">"C:\SomeFolder.zip"</span><span class="w"> </span><span class="nt">-Password</span><span class="w"> </span><span class="s2">"password123"</span><span class="w">
</span><span class="n">Write-ZipUsing7Zip</span><span class="w"> </span><span class="s2">"C:\Folder\*.txt"</span><span class="w"> </span><span class="s2">"C:\FoldersTxtFiles.zip"</span><span class="w"> </span><span class="nt">-HideWindow</span><span class="w">
</span></code></pre></div></div>

<p>I hope you find this useful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="7zip" /><category term="Archive" /><category term="Compress" /><category term="Password" /><category term="PowerShell" /><category term="Zip" /><summary type="html"><![CDATA[There are a few different ways to create zip files in powershell, but not many that allow you to create one that is password protected. I found this post that shows how to do it using 7zip, so I thought I would share my modified solution.]]></summary></entry><entry><title type="html">PowerShell Multi-Line Input Box Dialog, Open File Dialog, Folder Browser Dialog, Input Box, and Message Box</title><link href="https://blog.danskingdom.com/powershell-multi-line-input-box-dialog-open-file-dialog-folder-browser-dialog-input-box-and-message-box/" rel="alternate" type="text/html" title="PowerShell Multi-Line Input Box Dialog, Open File Dialog, Folder Browser Dialog, Input Box, and Message Box" /><published>2013-05-02T00:01:53+00:00</published><updated>2013-12-05T00:00:00+00:00</updated><id>https://blog.danskingdom.com/powershell-multi-line-input-box-dialog-open-file-dialog-folder-browser-dialog-input-box-and-message-box</id><content type="html" xml:base="https://blog.danskingdom.com/powershell-multi-line-input-box-dialog-open-file-dialog-folder-browser-dialog-input-box-and-message-box/"><![CDATA[<p>I love PowerShell, and when prompting users for input I often prefer to use GUI controls rather than have them enter everything into the console, as some things like browsing for files or folders or entering multi-line text aren’t very pleasing to do directly in the PowerShell prompt window. So I thought I’d share some PowerShell code that I often use for these purposes. Below I give the code for creating each type of GUI control from a function, an example of calling the function, and a screen shot of what the resulting GUI control looks like.</p>

<p><a href="/assets/Posts/2014/07/PowerShellGuiFunctions.zip">Download a file containing the code for the functions and examples shown here</a></p>

<h2 id="show-a-message-box">Show a message box</h2>

<p>Function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Show message box popup and return the button clicked by the user.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Read-MessageBoxDialog</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$Message</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$WindowTitle</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">System.Windows.Forms.MessageBoxButtons</span><span class="p">]</span><span class="nv">$Buttons</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.Windows.Forms.MessageBoxButtons</span><span class="p">]::</span><span class="n">OK</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">System.Windows.Forms.MessageBoxIcon</span><span class="p">]</span><span class="nv">$Icon</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.Windows.Forms.MessageBoxIcon</span><span class="p">]::</span><span class="n">None</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Windows.Forms</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">[</span><span class="n">System.Windows.Forms.MessageBox</span><span class="p">]::</span><span class="n">Show</span><span class="p">(</span><span class="nv">$Message</span><span class="p">,</span><span class="w"> </span><span class="nv">$WindowTitle</span><span class="p">,</span><span class="w"> </span><span class="nv">$Buttons</span><span class="p">,</span><span class="w"> </span><span class="nv">$Icon</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$buttonClicked</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Read-MessageBoxDialog</span><span class="w"> </span><span class="nt">-Message</span><span class="w"> </span><span class="s2">"Please press the OK button."</span><span class="w"> </span><span class="nt">-WindowTitle</span><span class="w"> </span><span class="s2">"Message Box Example"</span><span class="w"> </span><span class="nt">-Buttons</span><span class="w"> </span><span class="nx">OKCancel</span><span class="w"> </span><span class="nt">-Icon</span><span class="w"> </span><span class="nx">Exclamation</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$buttonClicked</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"OK"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Thanks for pressing OK"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You clicked </span><span class="nv">$buttonClicked</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2014/07/Message-Box-Example.png" alt="Message Box Example" /></p>

<h2 id="prompt-for-single-line-user-input">Prompt for single-line user input</h2>

<p>Function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Show input box popup and return the value entered by the user.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Read-InputBoxDialog</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$Message</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$WindowTitle</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$DefaultText</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">Microsoft.VisualBasic</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">[</span><span class="n">Microsoft.VisualBasic.Interaction</span><span class="p">]::</span><span class="n">InputBox</span><span class="p">(</span><span class="nv">$Message</span><span class="p">,</span><span class="w"> </span><span class="nv">$WindowTitle</span><span class="p">,</span><span class="w"> </span><span class="nv">$DefaultText</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$textEntered</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Read-InputBoxDialog</span><span class="w"> </span><span class="nt">-Message</span><span class="w"> </span><span class="s2">"Please enter the word 'Banana'"</span><span class="w"> </span><span class="nt">-WindowTitle</span><span class="w"> </span><span class="s2">"Input Box Example"</span><span class="w"> </span><span class="nt">-DefaultText</span><span class="w"> </span><span class="s2">"Apple"</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$textEntered</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You clicked Cancel"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="nv">$textEntered</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Banana"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Thanks for typing Banana"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You entered </span><span class="nv">$textEntered</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2014/07/Input-Box-Example.png" alt="Input Box Example" /></p>

<h2 id="prompt-for-a-file">Prompt for a file</h2>

<p>This is based on <a href="http://blogs.technet.com/b/heyscriptingguy/archive/2009/09/01/hey-scripting-guy-september-1.aspx">a post the Scripting Guy made</a>.</p>

<p>Function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Show an Open File Dialog and return the file selected by the user.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Read-OpenFileDialog</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$WindowTitle</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$InitialDirectory</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$Filter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"All files (*.*)|*.*"</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="nv">$AllowMultiSelect</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Windows.Forms</span><span class="w">
    </span><span class="nv">$openFileDialog</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Windows.Forms.OpenFileDialog</span><span class="w">
    </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">Title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$WindowTitle</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrWhiteSpace</span><span class="p">(</span><span class="nv">$InitialDirectory</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">InitialDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$InitialDirectory</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">Filter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$Filter</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$AllowMultiSelect</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">MultiSelect</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">ShowHelp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">    </span><span class="c"># Without this line the ShowDialog() function may hang depending on system configuration and running from console vs. ISE.</span><span class="w">
    </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">ShowDialog</span><span class="p">()</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$AllowMultiSelect</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">Filenames</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="nv">$openFileDialog</span><span class="o">.</span><span class="nf">Filename</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$filePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Read-OpenFileDialog</span><span class="w"> </span><span class="nt">-WindowTitle</span><span class="w"> </span><span class="s2">"Select Text File Example"</span><span class="w"> </span><span class="nt">-InitialDirectory</span><span class="w"> </span><span class="s1">'C:\'</span><span class="w"> </span><span class="nt">-Filter</span><span class="w"> </span><span class="s2">"Text files (*.txt)|*.txt"</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$filePath</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You selected the file: </span><span class="nv">$filePath</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"You did not select a file."</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2014/07/Select-Text-File-Example.png" alt="Select Text File Example" /></p>

<h2 id="prompt-for-a-directory">Prompt for a directory</h2>

<p>This is based on <a href="http://forums.anandtech.com/showthread.php?t=2314443">this post</a>, as using <code class="language-plaintext highlighter-rouge">System.Windows.Forms.FolderBrowserDialog</code> may hang depending on system configuration and running from the console vs. PS ISE.</p>

<p>Function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Show an Open Folder Dialog and return the directory selected by the user.</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Read-FolderBrowserDialog</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$Message</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$InitialDirectory</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="nv">$NoNewFolderButton</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$browseForFolderOptions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$NoNewFolderButton</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$browseForFolderOptions</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">512</span><span class="w"> </span><span class="p">}</span><span class="w">

    </span><span class="nv">$app</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nt">-ComObject</span><span class="w"> </span><span class="nx">Shell.Application</span><span class="w">
    </span><span class="nv">$folder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$app</span><span class="o">.</span><span class="nf">BrowseForFolder</span><span class="p">(</span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="nv">$Message</span><span class="p">,</span><span class="w"> </span><span class="nv">$browseForFolderOptions</span><span class="p">,</span><span class="w"> </span><span class="nv">$InitialDirectory</span><span class="p">)</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$folder</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$selectedDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$folder</span><span class="o">.</span><span class="nf">Self</span><span class="o">.</span><span class="nf">Path</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$selectedDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">[</span><span class="n">System.Runtime.Interopservices.Marshal</span><span class="p">]::</span><span class="n">ReleaseComObject</span><span class="p">(</span><span class="nv">$app</span><span class="p">)</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$selectedDirectory</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$directoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Read-FolderBrowserDialog</span><span class="w"> </span><span class="nt">-Message</span><span class="w"> </span><span class="s2">"Please select a directory"</span><span class="w"> </span><span class="nt">-InitialDirectory</span><span class="w"> </span><span class="s1">'C:\'</span><span class="w"> </span><span class="nt">-NoNewFolderButton</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="nv">$directoryPath</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You selected the directory: </span><span class="nv">$directoryPath</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"You did not select a directory."</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2014/07/Browse-For-Folder.png" alt="Browse For Folder" /></p>

<h2 id="prompt-for-multi-line-user-input">Prompt for multi-line user input</h2>

<p>This is based on <a href="http://technet.microsoft.com/en-us/library/ff730941.aspx">code shown in this TechNet article</a>.</p>

<p>Function:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Read-MultiLineInputBoxDialog</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$Message</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$WindowTitle</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$DefaultText</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Prompts the user with a multi-line input box and returns the text they enter, or null if they cancelled the prompt.

    </span><span class="cs">.PARAMETER</span><span class="cm"> Message
    The message to display to the user explaining what text we are asking them to enter.

    </span><span class="cs">.PARAMETER</span><span class="cm"> WindowTitle
    The text to display on the prompt window's title.

    </span><span class="cs">.PARAMETER</span><span class="cm"> DefaultText
    The default text to show in the input box.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    $userText = Read-MultiLineInputDialog "Input some text please:" "Get User's Input"

    Shows how to create a simple prompt to get mutli-line input from a user.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    # Setup the default multi-line address to fill the input box with.
    $defaultAddress = @'
    John Doe
    123 St.
    Some Town, SK, Canada
    A1B 2C3
    '@

    $address = Read-MultiLineInputDialog "Please enter your full address, including name, street, city, and postal code:" "Get User's Address" $defaultAddress
    if ($address -eq $null)
    {
        Write-Error "You pressed the Cancel button on the multi-line input box."
    }

    Prompts the user for their address and stores it in a variable, pre-filling the input box with a default multi-line address.
    If the user pressed the Cancel button an error is written to the console.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    $inputText = Read-MultiLineInputDialog -Message "If you have a really long message you can break it apart`nover two lines with the powershell newline character:" -WindowTitle "Window Title" -DefaultText "Default text for the input box."

    Shows how to break the second parameter (Message) up onto two lines using the powershell newline character (`n).
    If you break the message up into more than two lines the extra lines will be hidden behind or show ontop of the TextBox.

    </span><span class="cs">.NOTES</span><span class="cm">
    Name: Show-MultiLineInputDialog
    Author: Daniel Schroeder (originally based on the code shown at http://technet.microsoft.com/en-us/library/ff730941.aspx)
    Version: 1.0
#&gt;</span><span class="w">
    </span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Drawing</span><span class="w">
    </span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Windows.Forms</span><span class="w">

    </span><span class="c"># Create the Label.</span><span class="w">
    </span><span class="nv">$label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Windows.Forms.Label</span><span class="w">
    </span><span class="nv">$label</span><span class="o">.</span><span class="nf">Location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">10</span><span class="p">)</span><span class="w">
    </span><span class="nv">$label</span><span class="o">.</span><span class="nf">Size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">280</span><span class="p">,</span><span class="mi">20</span><span class="p">)</span><span class="w">
    </span><span class="nv">$label</span><span class="o">.</span><span class="nf">AutoSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
    </span><span class="nv">$label</span><span class="o">.</span><span class="nf">Text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$Message</span><span class="w">

    </span><span class="c"># Create the TextBox used to capture the user's text.</span><span class="w">
    </span><span class="nv">$textBox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Windows.Forms.TextBox</span><span class="w">
    </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">Location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">40</span><span class="p">)</span><span class="w">
    </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">Size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">575</span><span class="p">,</span><span class="mi">200</span><span class="p">)</span><span class="w">
    </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">AcceptsReturn</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
    </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">AcceptsTab</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">
    </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">Multiline</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
    </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">ScrollBars</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Both'</span><span class="w">
    </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">Text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$DefaultText</span><span class="w">

    </span><span class="c"># Create the OK button.</span><span class="w">
    </span><span class="nv">$okButton</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Windows.Forms.Button</span><span class="w">
    </span><span class="nv">$okButton</span><span class="o">.</span><span class="nf">Location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">415</span><span class="p">,</span><span class="mi">250</span><span class="p">)</span><span class="w">
    </span><span class="nv">$okButton</span><span class="o">.</span><span class="nf">Size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">75</span><span class="p">,</span><span class="mi">25</span><span class="p">)</span><span class="w">
    </span><span class="nv">$okButton</span><span class="o">.</span><span class="nf">Text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"OK"</span><span class="w">
    </span><span class="nv">$okButton</span><span class="o">.</span><span class="nf">Add_Click</span><span class="p">({</span><span class="w"> </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Tag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$textBox</span><span class="o">.</span><span class="nf">Text</span><span class="p">;</span><span class="w"> </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="p">})</span><span class="w">

    </span><span class="c"># Create the Cancel button.</span><span class="w">
    </span><span class="nv">$cancelButton</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Windows.Forms.Button</span><span class="w">
    </span><span class="nv">$cancelButton</span><span class="o">.</span><span class="nf">Location</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">510</span><span class="p">,</span><span class="mi">250</span><span class="p">)</span><span class="w">
    </span><span class="nv">$cancelButton</span><span class="o">.</span><span class="nf">Size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">75</span><span class="p">,</span><span class="mi">25</span><span class="p">)</span><span class="w">
    </span><span class="nv">$cancelButton</span><span class="o">.</span><span class="nf">Text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Cancel"</span><span class="w">
    </span><span class="nv">$cancelButton</span><span class="o">.</span><span class="nf">Add_Click</span><span class="p">({</span><span class="w"> </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Tag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="p">;</span><span class="w"> </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Close</span><span class="p">()</span><span class="w"> </span><span class="p">})</span><span class="w">

    </span><span class="c"># Create the form.</span><span class="w">
    </span><span class="nv">$form</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Windows.Forms.Form</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$WindowTitle</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Drawing.Size</span><span class="p">(</span><span class="mi">610</span><span class="p">,</span><span class="mi">320</span><span class="p">)</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">FormBorderStyle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'FixedSingle'</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">StartPosition</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"CenterScreen"</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">AutoSizeMode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'GrowAndShrink'</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Topmost</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$True</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">AcceptButton</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$okButton</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">CancelButton</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$cancelButton</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">ShowInTaskbar</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">

    </span><span class="c"># Add all of the controls to the form.</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Controls</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$label</span><span class="p">)</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Controls</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$textBox</span><span class="p">)</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Controls</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$okButton</span><span class="p">)</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Controls</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$cancelButton</span><span class="p">)</span><span class="w">

    </span><span class="c"># Initialize and show the form.</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Add_Shown</span><span class="p">({</span><span class="nv">$form</span><span class="o">.</span><span class="nf">Activate</span><span class="p">()})</span><span class="w">
    </span><span class="nv">$form</span><span class="o">.</span><span class="nf">ShowDialog</span><span class="p">()</span><span class="w"> </span><span class="err">&gt;</span><span class="w"> </span><span class="bp">$null</span><span class="w">  </span><span class="c"># Trash the text of the button that was clicked.</span><span class="w">

    </span><span class="c"># Return the text that the user entered.</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="nv">$form</span><span class="o">.</span><span class="nf">Tag</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Example:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$multiLineText</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Read-MultiLineInputBoxDialog</span><span class="w"> </span><span class="nt">-Message</span><span class="w"> </span><span class="s2">"Please enter some text. It can be multiple lines"</span><span class="w"> </span><span class="nt">-WindowTitle</span><span class="w"> </span><span class="s2">"Multi Line Example"</span><span class="w"> </span><span class="nt">-DefaultText</span><span class="w"> </span><span class="s2">"Enter some text here..."</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$multiLineText</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You clicked Cancel"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"You entered the following text: </span><span class="nv">$multiLineText</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><img src="/assets/Posts/2014/07/Multi-Line-Example.png" alt="Multi Line Example" /></p>

<p>All of these but the multi-line input box just use existing Windows Forms / Visual Basic controls.</p>

<p>I originally was using the Get verb to prefix the functions, then switched to the Show verb, but after <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms714428%28v=vs.85%29.aspx">reading through this page</a>, I decided that the Read verb is probably the most appropriate (and it lines up with the Read-Host cmdlet).</p>

<p>Hopefully you find this useful.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="PowerShell" /><category term="directory" /><category term="file" /><category term="GUI" /><category term="input" /><category term="input box" /><category term="message box" /><category term="multi line" /><category term="multiline" /><category term="PowerShell" /><category term="prompt" /><summary type="html"><![CDATA[I love PowerShell, and when prompting users for input I often prefer to use GUI controls rather than have them enter everything into the console, as some things like browsing for files or folders or entering multi-line text aren’t very pleasing to do directly in the PowerShell prompt window. So I thought I’d share some PowerShell code that I often use for these purposes. Below I give the code for creating each type of GUI control from a function, an example of calling the function, and a screen shot of what the resulting GUI control looks like.]]></summary></entry><entry><title type="html">Invoke-MsBuild Powershell Module</title><link href="https://blog.danskingdom.com/invoke-msbuild-powershell-module/" rel="alternate" type="text/html" title="Invoke-MsBuild Powershell Module" /><published>2013-04-05T23:05:53+00:00</published><updated>2013-04-05T23:05:53+00:00</updated><id>https://blog.danskingdom.com/invoke-msbuild-powershell-module</id><content type="html" xml:base="https://blog.danskingdom.com/invoke-msbuild-powershell-module/"><![CDATA[<p><strong>Update:</strong> I’ve moved this project to it’s own new home at https://invokemsbuild.codeplex.com. All updates will be made there.</p>

<p>I’ve spent a little while creating a powershell module that can be used to call MsBuild. It returns whether the build succeeded or not, and runs through the Visual Studio command prompt if possible, since some projects can’t be built by calling msbuild directly (e.g. XNA projects). It also provides several other parameters to do things like show the window performing the build, automatically open the build log if the build fails, etc.</p>

<p>Here is the script (<del>copy-paste the code into a file called Invoke-MsBuild.psm1</del> <a href="https://invokemsbuild.codeplex.com/releases/">go download the updated version</a>):</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Invoke-MsBuild</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Builds the given Visual Studio solution or project file using MSBuild.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Executes the MSBuild.exe tool against the specified Visual Studio solution or project file.
    Returns true if the build succeeded, false if the build failed.
    If using the PathThru switch, the process running MSBuild is returned instead.

    </span><span class="cs">.PARAMETER</span><span class="cm"> Path
    The path of the Visual Studio solution or project to build (e.g. a .sln or .csproj file).

    </span><span class="cs">.PARAMETER</span><span class="cm"> MsBuildParameters
    Additional parameters to pass to the MsBuild command-line tool. This can be any valid MsBuild command-line parameters except for the path of
    the solution/project to build.

http://msdn.microsoft.com/en-ca/library/vstudio/ms164311.aspx

    </span><span class="cs">.PARAMETER</span><span class="cm"> $BuildLogDirectoryPath
    The directory path to write the build log file to.
    Defaults to putting the log file in the users temp directory (e.g. C:\Users\[User Name]\AppData\Local\Temp).
    Use the keyword "PathDirectory" to put the log file in the same directory as the .sln or project file being built.

    </span><span class="cs">.PARAMETER</span><span class="cm"> AutoLaunchBuildLog
    If set, this switch will cause the build log to automatically be launched into the default viewer if the build fails.
    NOTE: This switch cannot be used with the PassThru switch.

    </span><span class="cs">.PARAMETER</span><span class="cm"> KeepBuildLogOnSuccessfulBuilds
    If set, this switch will cause the msbuild log file to not be deleted on successful builds; normally it is only kept around on failed builds.
    NOTE: This switch cannot be used with the PassThru switch.

    </span><span class="cs">.PARAMETER</span><span class="cm"> ShowBuildWindow
    If set, this switch will cause a command prompt window to be shown in order to view the progress of the build.

    </span><span class="cs">.PARAMETER</span><span class="cm"> ShowBuildWindowAndPromptForInputBeforeClosing
    If set, this switch will cause a command prompt window to be shown in order to view the progress of the build, and it will remain open
    after the build completes until the user presses a key on it.
    NOTE: If not using PassThru, the user will need to provide input before execution will return back to the calling script.

    </span><span class="cs">.PARAMETER</span><span class="cm"> PassThru
    If set, this switch will cause the script not to wait until the build (launched in another process) completes before continuing execution.
    Instead the build will be started in a new process and that process will immediately be returned, allowing the calling script to continue
    execution while the build is performed, and also to inspect the process to see when it completes.
    NOTE: This switch cannot be used with the AutoLaunchBuildLog or KeepBuildLogOnSuccessfulBuilds switches.

    </span><span class="cs">.PARAMETER</span><span class="cm"> GetLogPath
    If set, the build will not actually be performed.
    Instead it will just return the full path of the MsBuild Log file that would be created if the build is performed with the same parameters.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln"

    Perform the default MSBuild actions on the Visual Studio solution to build the projects in it.
    The PowerShell script will halt execution until MsBuild completes.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Some Folder\MySolution.sln" -PassThru

    Perform the default MSBuild actions on the Visual Studio solution to build the projects in it.
    The PowerShell script will not halt execution; instead it will return the process performing MSBuild actions back to the caller while the action is performed.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -MsBuildParameters "/target:Clean;Build" -ShowBuildWindow

    Cleans then Builds the given C# project.
    A window displaying the output from MsBuild will be shown so the user can view the progress of the build.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\MySolution.sln" -Params "/target:Clean;Build /property:Configuration=Release;Platform=x64;BuildInParallel=true /verbosity:Detailed /maxcpucount"

    Cleans then Builds the given solution, specifying to build the project in parallel in the Release configuration for the x64 platform.
    Here the shorter "Params" alias is used instead of the full "MsBuildParameters" parameter name.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -ShowBuildWindowAndPromptForInputBeforeClosing -AutoLaunchBuildLog

    Builds the given C# project.
    A window displaying the output from MsBuild will be shown so the user can view the progress of the build, and it will not close until the user
    gives the window some input. This function will also not return until the user gives the window some input, halting the powershell script execution.
    If the build fails, the build log will automatically be opened in the default text viewer.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -KeepBuildLogOnSuccessfulBuilds -AutoLaunchBuildLog

    Builds the given C# project.
    The build log will be saved in "C:\BuildLogs", and they will not be automatically deleted even if the build succeeds.
    If the build fails, the build log will automatically be opened in the default text viewer.

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath PathDirectory

    Builds the given C# project.
    The build log will be saved in "C:\Some Folder\", which is the same directory as the project being built (i.e. directory specified in the Path).

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Database\Database.dbproj" -P "/t:Deploy /p:TargetDatabase=MyDatabase /p:TargetConnectionString=`"Data Source=DatabaseServerName`;Integrated Security=True`;Pooling=False`" /p:DeployToDatabase=True"

    Deploy the Visual Studio Database Project to the database "MyDatabase".
    Here the shorter "P" alias is used instead of the full "MsBuildParameters" parameter name.
    The shorter alias' of the msbuild parameters are also used; "/t" instead of "/target", and "/p" instead of "/property".

    </span><span class="cs">.EXAMPLE</span><span class="cm">
    Invoke-MsBuild -Path "C:\Some Folder\MyProject.csproj" -BuildLogDirectoryPath "C:\BuildLogs" -GetLogPath

    Returns the full path to the MsBuild Log file that would be created if the build was ran with the same parameters.
    In this example the returned log path might be "C:\BuildLogs\MyProject.msbuild.log".
    If the BuildLogDirectoryPath was not provided, the returned log path might be "C:\Some Folder\MyProject.msbuild.log".

    </span><span class="cs">.NOTES</span><span class="cm">
    Name:   Invoke-MsBuild
    Author: Daniel Schroeder (originally based on the module at http://geekswithblogs.net/dwdii/archive/2011/05/27/part-2-automating-a-visual-studio-build-with-powershell.aspx)
    Version: 1.1
#&gt;</span><span class="w">
    </span><span class="p">[</span><span class="n">CmdletBinding</span><span class="p">(</span><span class="bp">DefaultParameterSetName</span><span class="o">=</span><span class="s2">"Wait"</span><span class="p">)]</span><span class="w">
    </span><span class="kr">param</span><span class="w">
    </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">,</span><span class="n">ValueFromPipeline</span><span class="o">=</span><span class="bp">$true</span><span class="p">,</span><span class="n">HelpMessage</span><span class="o">=</span><span class="s2">"The path to the file to build with MsBuild (e.g. a .sln or .csproj file)."</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateScript</span><span class="p">({</span><span class="n">Test-Path</span><span class="w"> </span><span class="bp">$_</span><span class="p">})</span><span class="err">]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Path</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"Params"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"P"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$MsBuildParameters</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"L"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$BuildLogDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">Temp</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">,</span><span class="n">ParameterSetName</span><span class="o">=</span><span class="s2">"Wait"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"AutoLaunch"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"A"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$AutoLaunchBuildLogOnFailure</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">,</span><span class="n">ParameterSetName</span><span class="o">=</span><span class="s2">"Wait"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">ValidateNotNullOrEmpty</span><span class="p">()]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"Keep"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"K"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$KeepBuildLogOnSuccessfulBuilds</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"Show"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"S"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$ShowBuildWindow</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"Prompt"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$ShowBuildWindowAndPromptForInputBeforeClosing</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">,</span><span class="n">ParameterSetName</span><span class="o">=</span><span class="s2">"PassThru"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$PassThru</span><span class="p">,</span><span class="w">

        </span><span class="p">[</span><span class="n">parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$false</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"Get"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">Alias</span><span class="p">(</span><span class="s2">"G"</span><span class="p">)]</span><span class="w">
        </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="w"> </span><span class="nv">$GetLogPath</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="kr">BEGIN</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">END</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">PROCESS</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="c"># Turn on Strict Mode to help catch syntax-related errors.</span><span class="w">
        </span><span class="c">#   This must come after a script's/function's param section.</span><span class="w">
        </span><span class="c">#   Forces a function to be the first non-comment code to appear in a PowerShell Script/Module.</span><span class="w">
        </span><span class="n">Set-StrictMode</span><span class="w"> </span><span class="nt">-Version</span><span class="w"> </span><span class="nx">Latest</span><span class="w">

        </span><span class="c"># If the keyword was supplied, place the log in the same folder as the solution/project being built.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$BuildLogDirectoryPath</span><span class="o">.</span><span class="nf">Equals</span><span class="p">(</span><span class="s2">"PathDirectory"</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">System.StringComparison</span><span class="p">]::</span><span class="nx">InvariantCultureIgnoreCase</span><span class="p">))</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$BuildLogDirectoryPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.Path</span><span class="p">]::</span><span class="n">GetDirectoryName</span><span class="p">(</span><span class="nv">$Path</span><span class="p">)</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># Store the VS Command Prompt to do the build in, if one exists.</span><span class="w">
        </span><span class="nv">$vsCommandPrompt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-VisualStudioCommandPromptPath</span><span class="w">

        </span><span class="c"># Local Variables.</span><span class="w">
        </span><span class="nv">$solutionFileName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$Path</span><span class="p">)</span><span class="o">.</span><span class="nf">Name</span><span class="w">
        </span><span class="nv">$buildLogFilePath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$BuildLogDirectoryPath</span><span class="w"> </span><span class="nv">$solutionFileName</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s2">".msbuild.log"</span><span class="w">
        </span><span class="nv">$windowStyle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$ShowBuildWindow</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$ShowBuildWindowAndPromptForInputBeforeClosing</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"Normal"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"Hidden"</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="nv">$buildCrashed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">;</span><span class="w">

        </span><span class="c"># If all we want is the path to the Log file that will be generated, return it.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$GetLogPath</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="kr">return</span><span class="w"> </span><span class="nv">$buildLogFilePath</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># Try and build the solution.</span><span class="w">
        </span><span class="kr">try</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># Build the arguments to pass to MsBuild.</span><span class="w">
            </span><span class="nv">$buildArguments</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"""</span><span class="nv">$Path</span><span class="s2">"" </span><span class="nv">$MsBuildParameters</span><span class="s2"> /fileLoggerParameters:LogFile=""</span><span class="nv">$buildLogFilePath</span><span class="s2">"""</span><span class="w">

            </span><span class="c"># If a VS Command Prompt was found, call MSBuild from that since it sets environmental variables that may be needed to build some projects.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$vsCommandPrompt</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="nv">$cmdArgumentsToRunMsBuild</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/k "" ""</span><span class="nv">$vsCommandPrompt</span><span class="s2">"" &amp; msbuild "</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="c"># Else the VS Command Prompt was not found, so just build using MSBuild directly.</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="c"># Get the path to the MsBuild executable.</span><span class="w">
                </span><span class="nv">$msBuildPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-MsBuildPath</span><span class="w">
                </span><span class="nv">$cmdArgumentsToRunMsBuild</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"/k "" ""</span><span class="nv">$msBuildPath</span><span class="s2">"" "</span><span class="w">
            </span><span class="p">}</span><span class="w">

            </span><span class="c"># Append the MSBuild arguments to pass into cmd.exe in order to do the build.</span><span class="w">
            </span><span class="nv">$pauseForInput</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$ShowBuildWindowAndPromptForInputBeforeClosing</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"Pause &amp; "</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">""</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="nv">$cmdArgumentsToRunMsBuild</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$buildArguments</span><span class="s2"> &amp; </span><span class="nv">$pauseForInput</span><span class="s2"> Exit"" "</span><span class="w">

            </span><span class="n">Write-Debug</span><span class="w"> </span><span class="s2">"Starting new cmd.exe process with arguments ""</span><span class="nv">$cmdArgumentsToRunMsBuild</span><span class="s2">""."</span><span class="w">

            </span><span class="c"># Perform the build.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$PassThru</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="kr">return</span><span class="w"> </span><span class="n">Start-Process</span><span class="w"> </span><span class="nx">cmd.exe</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="nv">$cmdArgumentsToRunMsBuild</span><span class="w"> </span><span class="nt">-WindowStyle</span><span class="w"> </span><span class="nv">$windowStyle</span><span class="w"> </span><span class="nt">-PassThru</span><span class="w">
            </span><span class="p">}</span><span class="w">
            </span><span class="kr">else</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">Start-Process</span><span class="w"> </span><span class="nx">cmd.exe</span><span class="w"> </span><span class="nt">-ArgumentList</span><span class="w"> </span><span class="nv">$cmdArgumentsToRunMsBuild</span><span class="w"> </span><span class="nt">-WindowStyle</span><span class="w"> </span><span class="nv">$windowStyle</span><span class="w"> </span><span class="nt">-Wait</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="kr">catch</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nv">$buildCrashed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">;</span><span class="w">
            </span><span class="nv">$errorMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$_</span><span class="w">
            </span><span class="n">Write-Error</span><span class="w"> </span><span class="p">(</span><span class="s2">"Unexpect error occured while building ""</span><span class="nv">$Path</span><span class="s2">"": </span><span class="nv">$errorMessage</span><span class="s2">"</span><span class="w"> </span><span class="p">);</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># If the build crashed, return that the build didn't succeed.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$buildCrashed</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="kr">return</span><span class="w"> </span><span class="bp">$false</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># Get if the build failed or not by looking at the log file.</span><span class="w">
        </span><span class="nv">$buildSucceeded</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">((</span><span class="n">Select-String</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$buildLogFilePath</span><span class="w"> </span><span class="nt">-Pattern</span><span class="w"> </span><span class="s2">"Build FAILED."</span><span class="w"> </span><span class="nt">-SimpleMatch</span><span class="p">)</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">

        </span><span class="c"># If the build succeeded.</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$buildSucceeded</span><span class="p">)</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># If we shouldn't keep the log around, delete it.</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$KeepBuildLogOnSuccessfulBuilds</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$buildLogFilePath</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="c"># Else at least one of the projects failed to build.</span><span class="w">
        </span><span class="kr">else</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="c"># Write the error message as a warning.</span><span class="w">
            </span><span class="n">Write-Warning</span><span class="w"> </span><span class="s2">"FAILED to build ""</span><span class="nv">$Path</span><span class="s2">"". Please check the build log ""</span><span class="nv">$buildLogFilePath</span><span class="s2">"" for details."</span><span class="w">

            </span><span class="c"># If we should show the build log automatically, open it with the default viewer.</span><span class="w">
            </span><span class="kr">if</span><span class="p">(</span><span class="nv">$AutoLaunchBuildLogOnFailure</span><span class="p">)</span><span class="w">
            </span><span class="p">{</span><span class="w">
                </span><span class="n">Start-Process</span><span class="w"> </span><span class="nt">-verb</span><span class="w"> </span><span class="s2">"Open"</span><span class="w"> </span><span class="nv">$buildLogFilePath</span><span class="p">;</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="c"># Return if the Build Succeeded or Failed.</span><span class="w">
        </span><span class="kr">return</span><span class="w"> </span><span class="nv">$buildSucceeded</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">function</span><span class="w"> </span><span class="n">Get</span><span class="nt">-VisualStudioCommandPromptPath</span><span class="w">
</span><span class="p">{</span><span class="w">
 </span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
        Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
        Gets the file path to the latest Visual Studio Command Prompt. Returns $null if a path is not found.
    #&gt;</span><span class="w">

</span><span class="c"># Get some environmental paths.</span><span class="w">
</span><span class="nv">$vs2010CommandPrompt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">VS100COMNTOOLS</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s2">"vcvarsall.bat"</span><span class="w">
</span><span class="nv">$vs2012CommandPrompt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">VS110COMNTOOLS</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s2">"VsDevCmd.bat"</span><span class="w">

</span><span class="c"># Store the VS Command Prompt to do the build in, if one exists.</span><span class="w">
</span><span class="nv">$vsCommandPrompt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$vs2012CommandPrompt</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$vsCommandPrompt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$vs2012CommandPrompt</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$vs2010CommandPrompt</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="nv">$vsCommandPrompt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$vs2010CommandPrompt</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Return the path to the VS Command Prompt if it was found.</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="nv">$vsCommandPrompt</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">function</span><span class="w"> </span><span class="n">Get</span><span class="nt">-MsBuildPath</span><span class="w">
</span><span class="p">{</span><span class="w">
 </span><span class="cm">&lt;#
    </span><span class="cs">.SYNOPSIS</span><span class="cm">
    Gets the path to the latest version of MsBuild.exe. Returns $null if a path is not found.

    </span><span class="cs">.DESCRIPTION</span><span class="cm">
    Gets the path to the latest version of MsBuild.exe. Returns $null if a path is not found.
#&gt;</span><span class="w">

</span><span class="c"># Array of valid MsBuild versions</span><span class="w">
</span><span class="nv">$Versions</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="s2">"4.0"</span><span class="p">,</span><span class="w"> </span><span class="s2">"3.5"</span><span class="p">,</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">)</span><span class="w">

</span><span class="c"># Loop through each version from largest to smallest</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$Version</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$Versions</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
    </span><span class="c"># Try to find an instance of that particular version in the registry</span><span class="w">
    </span><span class="nv">$RegKey</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\</span><span class="nv">${Version}</span><span class="s2">"</span><span class="w">
    </span><span class="nv">$ItemProperty</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ItemProperty</span><span class="w"> </span><span class="nv">$RegKey</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w">

    </span><span class="c"># If registry entry exsists, then get the msbuild path and retrun</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$ItemProperty</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="bp">$null</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="kr">return</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$ItemProperty</span><span class="o">.</span><span class="nf">MSBuildToolsPath</span><span class="w"> </span><span class="nt">-ChildPath</span><span class="w"> </span><span class="nx">MsBuild.exe</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Return that we were not able to find MsBuild.exe.</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">Export</span><span class="nt">-ModuleMember</span><span class="w"> </span><span class="nt">-Function</span><span class="w"> </span><span class="n">Invoke</span><span class="nt">-MsBuild</span><span class="w">
</span></code></pre></div></div>

<p>And here’s an example of how to use it (assuming you saved it to a file called “Invoke-MsBuild.psm1”):</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Import the module used to build the .sln and project files.</span><span class="w">
</span><span class="n">Import-Module</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="p">[</span><span class="n">DirectoryContainingModule</span><span class="p">]</span><span class="nx">\Invoke-MsBuild.psm1</span><span class="w">
</span><span class="n">Invoke-MsBuild</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"[Path to .sln file]"</span><span class="w"> </span><span class="nt">-MsBuildParameters</span><span class="w"> </span><span class="s2">"/target:Clean;Build /property:Configuration=Release;Platform=""Mixed Platforms"" /verbosity:Quiet"</span><span class="w">
</span></code></pre></div></div>

<p>If you have any suggestions, please comment.</p>

<p>Feel free to use this in your own scripts. Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="MSBuild" /><category term="PowerShell" /><category term="module" /><category term="MSBuild" /><category term="PowerShell" /><category term="script" /><summary type="html"><![CDATA[Update: I’ve moved this project to it’s own new home at https://invokemsbuild.codeplex.com. All updates will be made there.]]></summary></entry><entry><title type="html">Using MSBuild to publish a VS 2012 SSDT .sqlproj database project the same way as a VS 2010 .dbproj database project (using command line arguments to specify the database to publish to)</title><link href="https://blog.danskingdom.com/using-msbuild-to-publish-a-vs-2012-ssdt-sqlproj-database-project-the-same-way-as-a-vs-2010-dbproj-database-project/" rel="alternate" type="text/html" title="Using MSBuild to publish a VS 2012 SSDT .sqlproj database project the same way as a VS 2010 .dbproj database project (using command line arguments to specify the database to publish to)" /><published>2013-03-18T20:34:13+00:00</published><updated>2019-03-22T00:00:00+00:00</updated><id>https://blog.danskingdom.com/using-msbuild-to-publish-a-vs-2012-ssdt-sqlproj-database-project-the-same-way-as-a-vs-2010-dbproj-database-project</id><content type="html" xml:base="https://blog.danskingdom.com/using-msbuild-to-publish-a-vs-2012-ssdt-sqlproj-database-project-the-same-way-as-a-vs-2010-dbproj-database-project/"><![CDATA[<p>We recently upgraded from VS (Visual Studio) 2010 to VS 2012, and with it had to upgrade our .dbproj database project to a .sqlproj. When making the switch I realized that .sqlproj database projects do not support specifying the database to deploy to as MSBuild command line arguments; instead you have to pass in the path to an xml file that has the necessary information.</p>

<p>So with the old .dbproj database project, you could deploy it to a database using:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MSBuild /t:Deploy /p:TargetDatabase<span class="o">=</span><span class="s2">"[DbName]"</span><span class="p">;</span><span class="nv">TargetConnectionString</span><span class="o">=</span><span class="s2">"Data Source=[Db.Server];Integrated Security=True;Pooling=False"</span> /p:DeployToDatabase<span class="o">=</span><span class="s2">"True"</span> <span class="s2">"[PathToBranch]Database</span><span class="se">\D</span><span class="s2">atabase.dbproj"</span>
</code></pre></div></div>

<p>But with the new .sqlproj database project you have to do:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MSBuild /t:Publish /p:SqlPublishProfilePath<span class="o">=</span><span class="s2">"myPublishFile.publish.xml"</span> <span class="s2">"[PathToBranch]Database</span><span class="se">\D</span><span class="s2">atabase.sqlproj"</span>
</code></pre></div></div>

<p>Where “myPublishFile.publish.xml” contains the database server and name to publish to.</p>

<p>One other minor thing to note is that it is called “deploying” the database with .dbproj, and is called “publishing” the database with .sqlproj; so when I say Deploy or Publish, I mean the same thing.</p>

<p>We use TFS at my organization and while making new builds for our Test environment, we have the build process deploy the database solution to our various Test databases. This would mean that for us I would either need to:</p>

<p>1 - create a new [DbName].publish.xml file for each database, check it into source control, and update the build template to know about the new file, or</p>

<p>2 - update the file contents of our myPublishFile.publish.xml file dynamically during the build to replace the Database Name and Server in the file before publishing to the database (i.e. read in file contents, replace string, write file contents back to file, publish to DB, repeat).</p>

<p>Option 1 means more work every time I want to add a new Test database to publish to. Option 2 is better, but still means having to update my TF Build template and create a new activity to read/write the new contents to the file.</p>

<p>Instead, there is a 3rd option, which is to simply add the code below to the bottom of the .sqlproj file. This will add some new MSBuild targets to the .sqlproj that will allow us to specify the database name and connection string using similar MSBuild command line parameters that we used to deploy the .dbproj project.</p>

<p>The code presented here is <a href="http://huddledmasses.org/adventures-getting-msbuild-tfs-and-sql-server-data-tools-to-work-together/">based on this post</a>, but the author has closed the comments section on that post and has not replied to my emails about the bugs in his code and example, so I thought I would share my modified and enhanced solution.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c">&lt;!--
    Custom targets and properties added so that we can specify the database to publish to using command line parameters with VS 2012+ .sqlproj projects, like we did with VS 2010 .dbproj projects.
    This allows us to specify the MSBuild command-line parameters TargetDatabaseName, and TargetConnectionString when Publishing, and PublishToDatabase when Building.
    I also stumbled across the undocumented parameter, PublishScriptFileName, which can be used to specify the generated sql script file name, just like DeployScriptFileName used to in VS 2010 .dbproj projects.
    Taken from: https://blog.danskingdom.com/using-msbuild-to-publish-a-vs-2012-ssdt-sqlproj-database-project-the-same-way-as-a-vs-2010-dbproj-database-project/
  --&gt;</span>
  <span class="nt">&lt;PropertyGroup</span> <span class="na">Condition=</span><span class="s">"'$(TargetDatabaseName)' != '' Or '$(TargetConnectionString)' != ''"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;PublishToDatabase</span> <span class="na">Condition=</span><span class="s">"'$(PublishToDatabase)' == ''"</span><span class="nt">&gt;</span>False<span class="nt">&lt;/PublishToDatabase&gt;</span>
    <span class="nt">&lt;TargetConnectionStringXml</span> <span class="na">Condition=</span><span class="s">"'$(TargetConnectionString)' != ''"</span><span class="nt">&gt;</span>
      <span class="ni">&amp;lt;</span>TargetConnectionString xdt:Transform="Replace"<span class="ni">&amp;gt;</span>$(TargetConnectionString)<span class="ni">&amp;lt;</span>/TargetConnectionString<span class="ni">&amp;gt;</span>
    <span class="nt">&lt;/TargetConnectionStringXml&gt;</span>
    <span class="nt">&lt;TargetDatabaseXml</span> <span class="na">Condition=</span><span class="s">"'$(TargetDatabaseName)' != ''"</span><span class="nt">&gt;</span>
      <span class="ni">&amp;lt;</span>TargetDatabaseName xdt:Transform="Replace"<span class="ni">&amp;gt;</span>$(TargetDatabaseName)<span class="ni">&amp;lt;</span>/TargetDatabaseName<span class="ni">&amp;gt;</span>
    <span class="nt">&lt;/TargetDatabaseXml&gt;</span>
    <span class="nt">&lt;TransformPublishXml&gt;</span>
      <span class="ni">&amp;lt;</span>Project xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"<span class="ni">&amp;gt;</span>
      <span class="ni">&amp;lt;</span>PropertyGroup<span class="ni">&amp;gt;</span>$(TargetConnectionStringXml)$(TargetDatabaseXml)<span class="ni">&amp;lt;</span>/PropertyGroup<span class="ni">&amp;gt;</span>
      <span class="ni">&amp;lt;</span>/Project<span class="ni">&amp;gt;</span>
    <span class="nt">&lt;/TransformPublishXml&gt;</span>
    <span class="c">&lt;!-- In order to do a transform, we HAVE to change the SqlPublishProfilePath --&gt;</span>
    <span class="nt">&lt;SqlPublishProfilePath</span> <span class="na">Condition=</span><span class="s">"'$([System.IO.Path]::IsPathRooted($(SqlPublishProfilePath)))' == 'False'"</span><span class="nt">&gt;</span>$(MSBuildProjectDirectory)\$(SqlPublishProfilePath)<span class="nt">&lt;/SqlPublishProfilePath&gt;</span>
    <span class="nt">&lt;TransformOutputFile&gt;</span>$(MSBuildProjectDirectory)\Transformed_$(TargetDatabaseName).publish.xml<span class="nt">&lt;/TransformOutputFile&gt;</span>
    <span class="nt">&lt;TransformScope&gt;</span>$([System.IO.Path]::GetFullPath($(MSBuildProjectDirectory)))<span class="nt">&lt;/TransformScope&gt;</span>
    <span class="nt">&lt;TransformStackTraceEnabled</span> <span class="na">Condition=</span><span class="s">"'$(TransformStackTraceEnabled)'==''"</span><span class="nt">&gt;</span>False<span class="nt">&lt;/TransformStackTraceEnabled&gt;</span>
  <span class="nt">&lt;/PropertyGroup&gt;</span>
  <span class="nt">&lt;Target</span> <span class="na">Name=</span><span class="s">"AfterBuild"</span> <span class="na">Condition=</span><span class="s">"'$(PublishToDatabase)'=='True'"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;CallTarget</span> <span class="na">Targets=</span><span class="s">"Publish"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/Target&gt;</span>
  <span class="nt">&lt;UsingTask</span> <span class="na">TaskName=</span><span class="s">"ParameterizeTransformXml"</span> <span class="na">AssemblyFile=</span><span class="s">"$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll"</span> <span class="nt">/&gt;</span>
  <span class="c">&lt;!-- If TargetDatabaseName or TargetConnectionString, is passed in then we use the tokenize transform to create a parameterized sql publish file --&gt;</span>
  <span class="nt">&lt;Target</span> <span class="na">Name=</span><span class="s">"BeforePublish"</span> <span class="na">Condition=</span><span class="s">"'$(TargetDatabaseName)' != '' Or '$(TargetConnectionString)' != ''"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Message</span> <span class="na">Text=</span><span class="s">"TargetDatabaseName = '$(TargetDatabaseName)', TargetConnectionString = '$(TargetConnectionString)', PublishScriptFileName = '$(PublishScriptFileName)', Transformed Sql Publish Profile Path = '$(TransformOutputFile)'"</span> <span class="na">Importance=</span><span class="s">"high"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;Error</span> <span class="na">Condition=</span><span class="s">"!Exists($(SqlPublishProfilePath))"</span> <span class="na">Text=</span><span class="s">"The SqlPublishProfilePath '$(SqlPublishProfilePath)' does not exist, please specify a valid file using msbuild /p:SqlPublishProfilePath='Path'"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;ParameterizeTransformXml</span> <span class="na">Source=</span><span class="s">"$(SqlPublishProfilePath)"</span> <span class="na">IsSourceAFile=</span><span class="s">"True"</span> <span class="na">Transform=</span><span class="s">"$(TransformPublishXml)"</span> <span class="na">IsTransformAFile=</span><span class="s">"False"</span> <span class="na">Destination=</span><span class="s">"$(TransformOutputFile)"</span> <span class="na">IsDestinationAFile=</span><span class="s">"True"</span> <span class="na">Scope=</span><span class="s">"$(TransformScope)"</span> <span class="na">StackTrace=</span><span class="s">"$(TransformStackTraceEnabled)"</span> <span class="na">SourceRootPath=</span><span class="s">"$(MSBuildProjectDirectory)"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;PropertyGroup&gt;</span>
      <span class="nt">&lt;SqlPublishProfilePath&gt;</span>$(TransformOutputFile)<span class="nt">&lt;/SqlPublishProfilePath&gt;</span>
    <span class="nt">&lt;/PropertyGroup&gt;</span>
  <span class="nt">&lt;/Target&gt;</span>
</code></pre></div></div>

<p>You can <a href="/assets/Posts/2013/11/MsBuildTargetsToPublishSqlProjFromCommandLine.txt">get the code here</a> to avoid website copy-paste formatting problems.</p>

<p>So after adding this code at the bottom of the .sqlproj file (above the <code class="language-plaintext highlighter-rouge">&lt;/Project&gt;</code> tag though), you can now build and publish the database solution from the MSBuild command line using:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MSBuild /t:Build /p:TargetDatabaseName<span class="o">=</span><span class="s2">"[DbName]"</span><span class="p">;</span><span class="nv">TargetConnectionString</span><span class="o">=</span><span class="s2">"Data Source=[Db.Server];Integrated Security=True;Pooling=False"</span> /p:PublishToDatabase<span class="o">=</span><span class="s2">"True"</span> /p:SqlPublishProfilePath<span class="o">=</span><span class="s2">"Template.publish.xml"</span> <span class="s2">"[PathToBranch]</span><span class="se">\D</span><span class="s2">atabase</span><span class="se">\D</span><span class="s2">atabase.sqlproj"</span>
</code></pre></div></div>

<p>Here you can see the 3 new parameters that we’ve added being used: TargetDatabaseName, TargetConnectionString, and PublishToDatabase.</p>

<p>When the TargetDatabaseName or TargetConnectionString parameters are provided we generated a new transformed .publish.xml file, which is the same as the provided “Template.publish.xml” file, but with the database and connection string values replaced with the provided values.</p>

<p>The PublishToDatabase parameter allows us to publish to the database immediately after the project is built; without this you would have to first call MSBuild to Build the database project, and then call MSBuild again to Publish it (or perhaps using “/t:Build;Publish” would work, but I didn’t test that).</p>

<p>If you want to simply publish the database project without building first (generally not recommended), you can do:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MSBuild /t:Publish /p:TargetDatabaseName<span class="o">=</span><span class="s2">"[DbName]"</span><span class="p">;</span><span class="nv">TargetConnectionString</span><span class="o">=</span><span class="s2">"Data Source=[Db.Server];Integrated Security=True;Pooling=False"</span> /p:SqlPublishProfilePath<span class="o">=</span><span class="s2">"Template.publish.xml"</span> <span class="s2">"[PathToBranch]</span><span class="se">\D</span><span class="s2">atabase</span><span class="se">\D</span><span class="s2">atabase.sqlproj"</span>
</code></pre></div></div>

<p>Be careful though, since if you don’t do a Build first, any changes that have been made since the last time the .sqlproj file was built on your machine won’t be published to the database.</p>

<p>Notice that I still have to provide a path to the template publish.xml file to transform, and that the path to this file is relative to the .sqlproj file (in this example the Template.publish.xml and .sqlproj files are in the same directory). You can simply use one of the publish.xml files generated by Visual Studio, and then the TargetDatabaseName and TargetConnectionString xml element values will be replaced with those given in the command line parameters. This allows you to still define any other publish settings as usual in the xml file.</p>

<p>Also notice that the PublishToDatabase parameter is only used when doing a Build, not a Publish; providing it when doing a Publish will not hurt anything though.</p>

<p>While creating my solution, I also accidentally stumbled upon what seems to be an undocumented SSDT parameter, PublishScriptFileName. While the DeployScriptFileName parameter could be used in VS 2010 .dbproj projects to change the name of the generated .sql file, I noticed that changing its value in the .publish.xml file didn’t seem to have any affect at all (so I’m not really sure why Visual Studio puts it in there). I randomly decided to try passing in PublishScriptFileName from the command line, and blamo, it worked! I tried changing the <DeployScriptFileName> element in the .publish.xml file to <PublishScriptFileName>, but it still didn’t seem to have any effect.</PublishScriptFileName></DeployScriptFileName></p>

<p>So now if I wanted to deploy my database project to 3 separate databases, I could do so with the following code to first Build the project, and the Publish it to the 3 databases:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MSBuild /t:Build <span class="s2">"[PathToBranch]</span><span class="se">\D</span><span class="s2">atabase</span><span class="se">\D</span><span class="s2">atabase.sqlproj"</span>
MSBuild /t:Publish /p:TargetDatabaseName<span class="o">=</span><span class="s2">"[DbName1]"</span><span class="p">;</span><span class="nv">TargetConnectionString</span><span class="o">=</span><span class="s2">"Data Source=[Db.Server];Integrated Security=True;Pooling=False"</span> /p:PublishScriptFileName<span class="o">=</span><span class="s2">"[DbName1].sql"</span> /p:SqlPublishProfilePath<span class="o">=</span><span class="s2">"Template.publish.xml"</span> <span class="s2">"[PathToBranch]</span><span class="se">\D</span><span class="s2">atabase</span><span class="se">\D</span><span class="s2">atabase.sqlproj"</span>
MSBuild /t:Publish /p:TargetDatabaseName<span class="o">=</span><span class="s2">"[DbName2]"</span><span class="p">;</span><span class="nv">TargetConnectionString</span><span class="o">=</span><span class="s2">"Data Source=[Db.Server];Integrated Security=True;Pooling=False"</span> /p:PublishScriptFileName<span class="o">=</span><span class="s2">"[DbName2].sql"</span> /p:SqlPublishProfilePath<span class="o">=</span><span class="s2">"Template.publish.xml"</span> <span class="s2">"[PathToBranch]</span><span class="se">\D</span><span class="s2">atabase</span><span class="se">\D</span><span class="s2">atabase.sqlproj"</span>
MSBuild /t:Publish /p:TargetDatabaseName<span class="o">=</span><span class="s2">"[DbName3]"</span><span class="p">;</span><span class="nv">TargetConnectionString</span><span class="o">=</span><span class="s2">"Data Source=[Db.Server];Integrated Security=True;Pooling=False"</span> /p:PublishScriptFileName<span class="o">=</span><span class="s2">"[DbName3].sql"</span> /p:SqlPublishProfilePath<span class="o">=</span><span class="s2">"Template.publish.xml"</span> <span class="s2">"[PathToBranch]</span><span class="se">\D</span><span class="s2">atabase</span><span class="se">\D</span><span class="s2">atabase.sqlproj"</span>
</code></pre></div></div>

<p>You could also instead just call MSBuild using the Build target with the PublishToDatabase parameter (which might actually be the safer bet); whatever you prefer. I have found that once the database project is built once, as long as no changes are made to it then subsequent “builds” of the project only take a second or two since it detects that no changes have been made and skips doing the build.</p>

<p>If you have any questions or feedback, let me know.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="Database" /><category term="MSBuild" /><category term="SQL" /><category term="Visual Studio" /><category term=".sqlproj" /><category term="argument" /><category term="Build" /><category term="command line" /><category term="Database" /><category term="DB" /><category term="Deploy" /><category term="MSBuild" /><category term="parameter" /><category term="Publish" /><category term="SQL" /><category term="SSDT" /><category term="Visual Studio" /><summary type="html"><![CDATA[We recently upgraded from VS (Visual Studio) 2010 to VS 2012, and with it had to upgrade our .dbproj database project to a .sqlproj. When making the switch I realized that .sqlproj database projects do not support specifying the database to deploy to as MSBuild command line arguments; instead you have to pass in the path to an xml file that has the necessary information.]]></summary></entry><entry><title type="html">Force your ClickOnce app to update without prompting user – Now on NuGet!</title><link href="https://blog.danskingdom.com/force-your-clickonce-app-to-update-without-prompting-user-now-on-nuget/" rel="alternate" type="text/html" title="Force your ClickOnce app to update without prompting user – Now on NuGet!" /><published>2013-03-08T20:26:10+00:00</published><updated>2013-03-08T20:26:10+00:00</updated><id>https://blog.danskingdom.com/force-your-clickonce-app-to-update-without-prompting-user-now-on-nuget</id><content type="html" xml:base="https://blog.danskingdom.com/force-your-clickonce-app-to-update-without-prompting-user-now-on-nuget/"><![CDATA[<p>A while ago <a href="https://blog.danskingdom.com/force-clickonce-applications-to-automatically-update-without-prompting-user-automatically-update-minimumrequiredversion-using-powershell/">I blogged about a powershell script I made</a> that would automatically update your ClickOnce project file’s Minimum Required Version to the latest version of your application so that users would not be presented with the “There is an update for this application. Do you want to install it?” prompt; instead the application would just automatically download and install the update.  This is nice because it’s one less prompt the end-user has to see, and from a security standpoint it means that your users will be forced to always use the latest version of the app.</p>

<p>There was a bit of setup to get this to work.  You had to make sure the powershell script was in the proper directory and add a post-build event to your project.  I’m happy to announce that I’ve significantly updated the powershell script with more features and bug fixes, and <strong>you can now install it from NuGet, which will handle all of the setup for you</strong>.  So now making sure that your end-users are always using the latest version of your application is as easy as adding the <a href="https://nuget.org/packages/AutoUpdateProjectsMinimumRequiredClickOnceVersion">AutoUpdateProjectsMinimumRequiredClickOnceVersion NuGet package</a> to your ClickOnce project.</p>

<p><img src="/assets/Posts/2013/03/managenuget.png" alt="Manage NuGet" /></p>

<p><img src="/assets/Posts/2013/03/install.png" alt="Install" /></p>

<p><img src="/assets/Posts/2013/03/fileadded.png" alt="File Added" /></p>

<p>As you can see in the last screenshot, it adds a new “PostBuildScripts” folder to your project that contains the powershell script that is ran from the project’s post-build event.</p>

<p>A couple caveats to note.  Because this uses PowerShell it means that it can only be ran on Windows machines (sorry Mac and Unix).  Also, because the powershell script modifies the .csproj/.vbproj file outside of Visual Studio, the first time you do a build after creating a new ClickOnce version, if you have any files from that project open you will be prompted to reload the project.  In order to prevent this from closing your open tabs, I recommend installing <a href="http://visualstudiogallery.msdn.microsoft.com/6705affd-ca37-4445-9693-f3d680c92f38">Scott Hanselman’s Workspace Reloader Visual Studio extension</a>.</p>

<p>I’ve also created <a href="http://aupmrcov.codeplex.com/">a CodePlex site to host this open source project</a>.</p>

<p>I hope you find this helpful.  Feel free to leave a comment.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="ClickOnce" /><category term="NuGet" /><category term="Automatic" /><category term="ClickOnce" /><category term="Latest" /><category term="Minimum" /><category term="NuGet" /><category term="Required" /><category term="Update" /><category term="Version" /><summary type="html"><![CDATA[A while ago I blogged about a powershell script I made that would automatically update your ClickOnce project file’s Minimum Required Version to the latest version of your application so that users would not be presented with the “There is an update for this application. Do you want to install it?” prompt; instead the application would just automatically download and install the update.  This is nice because it’s one less prompt the end-user has to see, and from a security standpoint it means that your users will be forced to always use the latest version of the app.]]></summary></entry><entry><title type="html">Too Many AutoHotkey Shortcuts To Remember? There’s An App For That!</title><link href="https://blog.danskingdom.com/too-many-autohotkey-shortcuts-to-remember-theres-an-app-for-that/" rel="alternate" type="text/html" title="Too Many AutoHotkey Shortcuts To Remember? There’s An App For That!" /><published>2013-01-19T21:59:22+00:00</published><updated>2013-01-19T21:59:22+00:00</updated><id>https://blog.danskingdom.com/too-many-autohotkey-shortcuts-to-remember-theres-an-app-for-that</id><content type="html" xml:base="https://blog.danskingdom.com/too-many-autohotkey-shortcuts-to-remember-theres-an-app-for-that/"><![CDATA[<p>I love <a href="http://www.autohotkey.com/">AutoHotkey</a> (AHK). Ever since I discovered it a little over a year ago I am constantly surprised and pleased with what I am able to accomplish with it. And there in lied my problem. Out of the box, AHK allows you to trigger your own scripts using hotkeys. My problem was that I had so many of these little (and some large) scripts to do so many things, that i quickly ran out of hotkeys to use that wouldn’t interfere with other application’s shortcut keys (Visual Studio anyone); also even if I had more hotkeys available, trying to remember which ones did what was a nightmare. To remedy this, I created <a href="http://ahkcommandpicker.codeplex.com/">AHK Command Picker</a>.</p>

<p><a href="http://ahkcommandpicker.codeplex.com/">AHK Command Picker</a> is really just a little UI that allows you to quickly run your scripts; so instead of using a hotkey, you just hit the Caps Lock key to bring up the UI, and then start typing for the script that you want to run. It provides Camel Case filtering to still make launching scripts super fast, and also shows your scripts in a list, allowing you to browse through them. It also allows to you easily pass parameters into your scripts, which can be used to change a script’s behaviour. Here’s a screenshot showing the UI and some of the many scripts that I have:</p>

<p><img src="/assets/Posts/2013/01/ahkcommandpicker-allcommands.png" alt="AHKCommandPicker-AllCommands" /></p>

<p>And here’s a screenshot showing it filter the list as you type:</p>

<p><img src="/assets/Posts/2013/01/ahkcommandpicker-filteredcommands.png" alt="AHKCommandPicker-FilteredCommands" /></p>

<p>One of my favourite things about AHK is the community. There are so many scripts out there that before you write your own code, you should just do a quick Google search and chances are you’ll find someone who has already written the script for you, or one that is close to your needs and can be quickly modified. I assumed this would be the case for the type of tool I was looking for. So I searched and came across HotKeyIt and Keyword Launcher, both of which were interesting and neat, but neither provided a GUI to quickly see your list of scripts so that you could launch it quickly. They still relied on you to remember your specific hotkeys; I wanted a list that I could <strong>browse</strong> my scripts from to easily launch them.</p>

<p>I love programming and learning new languages, so I thought I’d give creating the type of picker that I wanted a shot. It’s still not perfect, and I have many ideas for new features that could make it better, but using it as it is today has improved my productivity so much. I’ve shown it to some people at my work and many of them started using it and agree.</p>

<p>If all you are looking for is a program to quickly launch applications or open files, then I would recommend <a href="http://www.keybreeze.com/">KeyBreeze</a>. It’s a great little free app written in C# and is very polished (and much better than <a href="http://www.launchy.net/">Launchy</a> in my opinion because you can add ANY file/app to it via the context menu). But <strong>if you use AHK and want to be able to quickly launch your own scripts</strong>, definitely check out <a href="http://ahkcommandpicker.codeplex.com/">AHK Command Picker</a>. Like myself you will probably find that you go from using AHK to launch 10 – 20 scripts, to using it to launch hundreds or even thousands of scripts, saving you time and effort constantly throughout your day. Those things that you maybe only do once or twice a month and didn’t want to dedicate a hotkey to will start showing up in your scripts list.</p>

<p>So go ahead and give <a href="http://ahkcommandpicker.codeplex.com/">AHK Command Picker</a> a try, and be sure to let me know what you think of it and what improvements you would like to see by logging it on the Codeplex site.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="Productivity" /><category term="AHK" /><category term="AutoHotkey" /><category term="Automate" /><category term="Command" /><category term="Command Picker" /><category term="GUI" /><category term="Picker" /><summary type="html"><![CDATA[I love AutoHotkey (AHK). Ever since I discovered it a little over a year ago I am constantly surprised and pleased with what I am able to accomplish with it. And there in lied my problem. Out of the box, AHK allows you to trigger your own scripts using hotkeys. My problem was that I had so many of these little (and some large) scripts to do so many things, that i quickly ran out of hotkeys to use that wouldn’t interfere with other application’s shortcut keys (Visual Studio anyone); also even if I had more hotkeys available, trying to remember which ones did what was a nightmare. To remedy this, I created AHK Command Picker.]]></summary></entry><entry><title type="html">Making document storage in Sharepoint a breeze (leave the Web UI behind)</title><link href="https://blog.danskingdom.com/making-document-storage-in-sharepoint-a-breeze-leave-the-web-ui-behind/" rel="alternate" type="text/html" title="Making document storage in Sharepoint a breeze (leave the Web UI behind)" /><published>2012-12-05T22:12:52+00:00</published><updated>2012-12-05T22:12:52+00:00</updated><id>https://blog.danskingdom.com/making-document-storage-in-sharepoint-a-breeze-leave-the-web-ui-behind</id><content type="html" xml:base="https://blog.danskingdom.com/making-document-storage-in-sharepoint-a-breeze-leave-the-web-ui-behind/"><![CDATA[<p>Hey everyone, I know many of us regularly use Sharepoint for document storage in order to make documents available to several people, have it version controlled, etc. Doing this through the Web UI can be a real headache, especially when you have multiple documents you want to modify or upload, or when IE isn’t your default browser. Luckily we can access the Sharepoint library like a regular network drive if we like.</p>

<p>Open Sharepoint in Internet Explorer (other browsers don’t support the Open with Explorer functionality), navigate to wherever your documents are stored, choose the <strong>Library</strong> tab, and then click <strong>Open with Explorer</strong>.</p>

<p><img src="/assets/Posts/2012/12/clip_image001.png" alt="Open with Explorer" /></p>

<p>This will open the document storage in Explorer and you can interact with the documents just like they were on any other network drive J This makes uploading large numbers of documents or directory structures super easy (a simple copy-paste), and modifying your files nice and easy.</p>

<p><img src="/assets/Posts/2012/12/clip_image002.png" alt="File Explorer" /></p>

<p>As an added bonus, you can drag and drop that location from the address bar in Explorer to the Favorites menu so that it’s always easily accessible and you can leave the Sharepoint Web UI behind completely for modifying your documents. Just click on the new favorite to go straight to your documents.</p>

<p><img src="/assets/Posts/2012/12/clip_image003.png" alt="Add to Favorites" /></p>

<p><img src="/assets/Posts/2012/12/clip_image004.png" alt="Added to Favorites" /></p>

<p>You can even map this folder location as a network drive if you want to have it show up as another drive (e.g N: drive).</p>

<p>I hope you found this as useful as I did :-)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Sharepoint" /><category term="document" /><category term="network drive" /><category term="Sharepoint" /><category term="storage" /><summary type="html"><![CDATA[Hey everyone, I know many of us regularly use Sharepoint for document storage in order to make documents available to several people, have it version controlled, etc. Doing this through the Web UI can be a real headache, especially when you have multiple documents you want to modify or upload, or when IE isn’t your default browser. Luckily we can access the Sharepoint library like a regular network drive if we like.]]></summary></entry><entry><title type="html">Have Windows Automatically Login Without Entering Your Password</title><link href="https://blog.danskingdom.com/have-windows-automatically-login-without-entering-your-password/" rel="alternate" type="text/html" title="Have Windows Automatically Login Without Entering Your Password" /><published>2012-12-04T16:44:57+00:00</published><updated>2012-12-04T16:44:57+00:00</updated><id>https://blog.danskingdom.com/have-windows-automatically-login-without-entering-your-password</id><content type="html" xml:base="https://blog.danskingdom.com/have-windows-automatically-login-without-entering-your-password/"><![CDATA[<p>If you are like me and don’t want to have to enter your password each time Windows loads, you can have Windows start up without prompting you to enter a user name or password. The simple (and BAD) way to do this is to simply not have a password on your user account, but that’s a big security risk and will allow people to easily remote desktop into your computer.</p>

<p>So, first set a password on your windows account if you don’t already have one. Then select <code class="language-plaintext highlighter-rouge">Run...</code> from the start menu (or use Windows Key + R to open the Run window) and type <code class="language-plaintext highlighter-rouge">control userpasswords2</code>, which will open the user accounts application.</p>

<p><img src="/assets/Posts/2012/12/image.png" alt="Run command" /></p>

<p>On the Users tab, clear the box for <code class="language-plaintext highlighter-rouge">Users must enter a user name and password to use this computer</code>, and click on OK. An Automatically Log On dialog box will appear; enter the user name and password for the account you want to use to automatically log into Windows. That’s it.</p>

<p><img src="/assets/Posts/2012/12/image1.png" alt="User Accounts window" /></p>

<p>You may also want to make sure your screen saver is not set to prompt you for a password when it exits either.</p>

<p><img src="/assets/Posts/2012/12/image2.png" alt="Screen Saver Settings window" /></p>

<p>Now your computer is secure without getting in your way. :-)</p>

<p><strong>A word of caution about this</strong>: ANYBODY will be able to get into your computer. This is probably fine for your home desktop PCs, but you may want to think about enabling this on your laptop, especially if you regularly take it out and about and it has sensitive information on it (ID, credit card info, saved usernames/passwords in your web browser). If you weren’t using a password before anyways though, using this trick is still more secure than without it ;-)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows" /><category term="No Password" /><category term="Password" /><category term="Windows" /><summary type="html"><![CDATA[If you are like me and don’t want to have to enter your password each time Windows loads, you can have Windows start up without prompting you to enter a user name or password. The simple (and BAD) way to do this is to simply not have a password on your user account, but that’s a big security risk and will allow people to easily remote desktop into your computer.]]></summary></entry><entry><title type="html">Migrating My GWB Blog Over To WordPress</title><link href="https://blog.danskingdom.com/migrating-my-gwb-blog-over-to-wordpress/" rel="alternate" type="text/html" title="Migrating My GWB Blog Over To WordPress" /><published>2012-11-20T22:49:45+00:00</published><updated>2012-11-20T22:49:45+00:00</updated><id>https://blog.danskingdom.com/migrating-my-gwb-blog-over-to-wordpress</id><content type="html" xml:base="https://blog.danskingdom.com/migrating-my-gwb-blog-over-to-wordpress/"><![CDATA[<p>Geeks With Blogs has been good to me, but there are too many tempting things about Word Press for me to stay with GWB. Particularly their statistics features, and also I like that I can apply specific tags to my posts to make them easier to find in Google (and I never was able to get categories working on GWB).</p>

<p>The one thing I don’t like about WordPress is I can’t seem to find a theme that stretches the Content area to take up the rest of the space on wide resolutions…..hopefully I’ll be able to overcome that obstacle soon though.</p>

<p>For those who are curious as to how I actually moved all of my posts across, I ended up just using Live Writer to open my GWB posts, and then just changed it to publish to my WordPress blog. This was fairly painless, but with my 27 posts it took probably about 2 hours of manual effort to do. Most of the time WordPress was automatically able to copy the images over, but sometimes I had to save the pics from my GWB posts and then re-add them in Live Writer to my WordPress post. I found another developers automated solution (in alpha mode), but opted to do it manually since I wanted to manually specify Categories and Tags on each post anyways. The one thing I still have left to do is move the worthwhile comments across from the GWB posts to the new WordPress posts.</p>

<p>The largest pain point was that with GWB I was using the Code Snippet Plugin for Live Writer for my source code snippets, and when they got transferred over to WordPress they looked horrible. So I ended up finding a new <a href="http://richhewlett.com/wlwsourcecodeplugin/">Live Writer plugin called SyntaxHighlighter</a> that looks even nicer on my posts :-)</p>

<p>If anybody knows of a nice WordPress theme that stretches the content area to fit the width of the screen, please let me know. All of the themes I’ve found seem to have a max width set on them, so I end up with much wasted space on the left and right sides. Since I post lots of code snippets, the more horizontal space the better!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows Live Writer" /><category term="WordPress" /><category term="Geeks With Blogs" /><category term="GWB" /><category term="WordPress" /><summary type="html"><![CDATA[Geeks With Blogs has been good to me, but there are too many tempting things about Word Press for me to stay with GWB. Particularly their statistics features, and also I like that I can apply specific tags to my posts to make them easier to find in Google (and I never was able to get categories working on GWB).]]></summary></entry><entry><title type="html">Get AutoHotkey Script To Run As Admin At Startup</title><link href="https://blog.danskingdom.com/get-autohotkey-script-to-run-as-admin-at-startup/" rel="alternate" type="text/html" title="Get AutoHotkey Script To Run As Admin At Startup" /><published>2012-11-06T20:16:00+00:00</published><updated>2012-11-06T20:16:00+00:00</updated><id>https://blog.danskingdom.com/get-autohotkey-script-to-run-as-admin-at-startup</id><content type="html" xml:base="https://blog.danskingdom.com/get-autohotkey-script-to-run-as-admin-at-startup/"><![CDATA[<p><strong>Update</strong>: Before you go running your script as an admin, see if <a href="https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin/">this less obtrusive fix</a> will solve your problems.</p>

<p>A few weeks back I posted <a href="https://blog.danskingdom.com/autohotkey-cannot-interact-with-windows-8-windowsor-can-it/">some problems with running AutoHotkey (AHK) in Windows 8, and that the solution was to run your AHK script as admin</a>. I also showed how to have the script start automatically when you logged into Windows. What I didn’t realize at the time though was that the method only worked for launching my AHK script as an admin because I had disabled UAC in the registry (which prevents most Metro apps from working in Windows 8, and likely isn’t acceptable for most people).</p>

<p>So here is a Windows 8, UAC-friendly method to automatically launch your AHK scripts as admin at startup (also works in previous versions of Windows). The trick is to use the Task Scheduler:</p>

<ol>
  <li>Open the Task Scheduler (also known as “Schedule tasks” in Windows 8 Settings).
 <img src="/assets/Posts/2012/11/open-task-scheduler.png" alt="Open Task Scheduler" /></li>
  <li>Create a new Basic Task.</li>
  <li>Give it a name and description (something like “launch AutoHotkey script at login”), and then specify to have it run “When I log on”. Then specify that you want it to “Start a program”, and then point it towards your AutoHotkey script. Before you finish the wizard, check off “Open the Properties dialog for this task when I click Finish”.
 <img src="/assets/Posts/2012/11/create-basic-task-in-task-scheduler1.png" alt="Create Basic Task in Task Scheduler" /></li>
  <li>When that Properties dialog opens up, go to the Conditions tab and make sure none of the checkboxes under the Power category are checked off; this will ensure the script still launches if you are on a laptop and not plugged into AC power.
 <img src="/assets/Posts/2012/11/basic-task-conditions1.png" alt="Basic Task Conditions" /></li>
  <li>
    <p><strong>Now here is the important part</strong>: To have your script “Run as admin”, on the General tab check off “Run with highest privileges”.</p>

    <p><img src="/assets/Posts/2012/11/run-scheduled-task-as-admin_thumb3.png" alt="Run Scheduled Task as Admin" /></p>

    <p>Now your AHK script should start automatically as soon as you log into Windows; even when UAC is enabled :-).</p>
  </li>
  <li>
    <p>If your AHK script uses an <strong>#Include</strong> statement to include other files, you may get an error similar to this one when your task runs:</p>

    <blockquote>
      <p>#Include file … cannot be opened. The program will exit.</p>
    </blockquote>

    <p><img src="/assets/Posts/2012/11/ahk-cannot-open-include-file.png" alt="AHK Cannot Open Include File" /></p>

    <p>The solution to this is to tell your AHK script to start in the same directory as the file that you want to include. So you will need to edit your scheduled task’s Action to specify the Start In directory.</p>

    <p><img src="/assets/Posts/2012/11/task-scheduler-start-in-directory.png" alt="Task Scheduler Start In Directory" /></p>
  </li>
</ol>

<p>Happy coding!</p>

<h2 id="update">Update</h2>

<p>What I failed to realize earlier was that by default the Task Scheduler runs it’s programs in non-interactive mode, so they may run as the correct user, <strong>but in a different user session</strong>. Since most AHK scripts are interactive (i.e. they respond to user input), this means that your script may not work exactly as it should all of the time. For example, my AHK scripts were not able to launch ClickOnce applications.</p>

<p>The fix is to create your Scheduled Task in interactive mode. Unfortunately you cannot do this through the GUI; it must be done through the command line. So if you open up a command prompt you can use the following command to create a new interactive scheduled task:</p>

<blockquote>
  <p>schtasks /Create /RU “[Domain][Username]” /SC ONLOGON /TN “[Task Name]” /TR “[Path to program to run]” /IT /V1</p>
</blockquote>

<p>for example:</p>

<blockquote>
  <p>schtasks /Create /RU “Dan Schroeder” /SC ONLOGON /TN “Launch AHK Command Picker” /TR “D:AHKStuffAHKCommandPicker.ahk” /IT /V1</p>
</blockquote>

<p>The /IT switch is what tells it to create the task in Interactive mode. The /V1 switch actually specifies to create the task as a Windows XP/2000/Server 2003 compatible task, and has the added benefit of making the task run as admin by default with the Start In directory specified as the directory holding the file to run (i.e. steps 5 and 6 above; you will still need to do step 4 though).</p>

<p>If you already have your Scheduled Task created, you can simply make it interactive with the command:</p>

<blockquote>
  <p>schtasks /Change /TN “[Task Name]” /IT</p>
</blockquote>

<p>I hope you find this as helpful as I did!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="Windows 8" /><category term="AHK" /><category term="AutoHotkey" /><category term="Run As Admin" /><category term="Run at Startup" /><category term="Scheduled Tasks" /><category term="Task Scheduler" /><category term="Windows 8" /><summary type="html"><![CDATA[Update: Before you go running your script as an admin, see if this less obtrusive fix will solve your problems.]]></summary></entry><entry><title type="html">Stop YOUR emails from starting those company-wide Reply All email threads</title><link href="https://blog.danskingdom.com/stop-your-emails-from-starting-those-company-wide-reply-all-email-threads/" rel="alternate" type="text/html" title="Stop YOUR emails from starting those company-wide Reply All email threads" /><published>2012-10-11T20:15:00+00:00</published><updated>2025-11-17T00:00:00+00:00</updated><id>https://blog.danskingdom.com/stop-your-emails-from-starting-those-company-wide-reply-all-email-threads</id><content type="html" xml:base="https://blog.danskingdom.com/stop-your-emails-from-starting-those-company-wide-reply-all-email-threads/"><![CDATA[<p>You know you’ve seen it before; somebody sends out a company-wide email (or email to a large audience), and a small group of people start Reply-All-ing back to the email with info or jokes that is only relative to that small group of people, yet EVERYBODY on the original email list has to suffer their inbox filling up with what is essentially spam since it doesn’t pertain to them.</p>

<p>A co-worker of mine made an ingenious off-hand comment to me one day of how to avoid this, and I’ve been using it ever since.
Simply place the email addresses of everybody that you are sending the email to in the <code class="language-plaintext highlighter-rouge">BCC</code> field (<em>not</em> the <code class="language-plaintext highlighter-rouge">CC</code> field), and in the <code class="language-plaintext highlighter-rouge">TO</code> field put <em>your</em> email address.
So everybody still gets the email, and if anyone does a Reply or Reply All back, <em>only you</em> are notified.
You can then determine if a followup email to the entire group is warranted.
Note that the email recipients will not be able to see who else the email was sent it to, so you may want to include that info in the email subject or body.</p>

<p><img src="/assets/Posts/2012/10/outlook-email-bcc-all-staff-screenshot.png" alt="Screenshot of an Outlook email using the BCC field to send email to all staff" /></p>

<p>Obviously, you will not want to use this <em>all</em> the time; there are some times when you <em>want</em> a group discussion to occur over email.
But for those other times you can use this trick, such as when sending a NWR (Non-Work Related) email about the car you are selling, asking everyone what a good local restaurant near by is, collecting personal info from people, or sharing a handy program or trick you learnt about (such as this one 😉).
This trick can save everybody frustration and avoid wasting their time.
Trust me, your coworkers will thank you; mine did 😊</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Email" /><category term="Email" /><category term="Reply All" /><summary type="html"><![CDATA[You know you’ve seen it before; somebody sends out a company-wide email (or email to a large audience), and a small group of people start Reply-All-ing back to the email with info or jokes that is only relative to that small group of people, yet EVERYBODY on the original email list has to suffer their inbox filling up with what is essentially spam since it doesn’t pertain to them.]]></summary></entry><entry><title type="html">Get the Windows 7 Start Menu Back in Windows 8 with Classic Shell</title><link href="https://blog.danskingdom.com/get-the-windows-7-start-menu-back-in-windows-8-with-classic-shell/" rel="alternate" type="text/html" title="Get the Windows 7 Start Menu Back in Windows 8 with Classic Shell" /><published>2012-10-03T20:13:00+00:00</published><updated>2012-10-03T20:13:00+00:00</updated><id>https://blog.danskingdom.com/get-the-windows-7-start-menu-back-in-windows-8-with-classic-shell</id><content type="html" xml:base="https://blog.danskingdom.com/get-the-windows-7-start-menu-back-in-windows-8-with-classic-shell/"><![CDATA[<p>I just thought I’d give readers a heads up that you can use <a href="http://classicshell.sourceforge.net/">Classic Shell</a> to get the old Windows 7 style of start menu back instead of using the new crappy Windows 8 metro start screen; plus it’s free and open source :-) You can still access the metro start screen with Shift+WindowsKey or Shift clicking the start menu button. This program also defaults to showing the desktop on startup instead of the metro start screen (but that can be disabled), and is super configurable and has tons of options to tweak it just the way you want if you are picky like me :-) The only thing I would say is when installing the app, only install the Classic Start Menu; you don’t need the other ones.</p>

<p><img src="/assets/Posts/2012/11/clip_image002.jpg" alt="Classic Shell start menu" /> <img src="/assets/Posts/2012/11/clip_image004.jpg" alt="Classic Shell setup" /></p>

<p>You’re welcome, and happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Windows 8" /><category term="Classic Shell" /><category term="Start" /><category term="Start Button" /><category term="Start Menu" /><category term="Windows 8" /><summary type="html"><![CDATA[I just thought I’d give readers a heads up that you can use Classic Shell to get the old Windows 7 style of start menu back instead of using the new crappy Windows 8 metro start screen; plus it’s free and open source :-) You can still access the metro start screen with Shift+WindowsKey or Shift clicking the start menu button. This program also defaults to showing the desktop on startup instead of the metro start screen (but that can be disabled), and is super configurable and has tons of options to tweak it just the way you want if you are picky like me :-) The only thing I would say is when installing the app, only install the Classic Start Menu; you don’t need the other ones.]]></summary></entry><entry><title type="html">Adding and accessing custom sections in your C# App.config</title><link href="https://blog.danskingdom.com/adding-and-accessing-custom-sections-in-your-c-app-config/" rel="alternate" type="text/html" title="Adding and accessing custom sections in your C# App.config" /><published>2012-09-25T20:04:00+00:00</published><updated>2012-09-25T20:04:00+00:00</updated><id>https://blog.danskingdom.com/adding-and-accessing-custom-sections-in-your-c-app-config</id><content type="html" xml:base="https://blog.danskingdom.com/adding-and-accessing-custom-sections-in-your-c-app-config/"><![CDATA[<p><strong>Update (Feb 10, 2016):</strong> I found a NuGet package called <a href="https://github.com/spadger/simple-config/">simple-config</a> that allows you to dynamically bind a section in your web/app.config file to a strongly typed class without having to write all of the boiler-plate code that I show here. This may be an easier solution for you than going through the code I show below in this post.</p>

<p>So I recently thought I’d try using the app.config file to specify some data for my application (such as URLs) rather than hard-coding it into my app, which would require a recompile and redeploy of my app if one of our URLs changed.  By using the app.config it allows a user to just open up the .config file that sits beside their .exe file and edit the URLs right there and then re-run the app; no recompiling, no redeployment necessary.</p>

<p>I spent a good few hours fighting with the app.config and looking at examples on Google before I was able to get things to work properly.  Most of the examples I found showed you how to pull a value from the app.config if you knew the specific key of the element you wanted to retrieve, but it took me a while to find a way to simply loop through all elements in a section, so I thought I would share my solutions here.</p>

<p>Due to the popularity of this post, I have created a sample solution that shows the full implementation of both of the methods mentioned below.</p>

<p><a href="/assets/Posts/2014/02/AppConfigExample1.zip">Download the source code example</a></p>

<h2 id="simple-and-easy">Simple and Easy</h2>

<p>The easiest way to use the app.config is to use the built-in types, such as <code class="language-plaintext highlighter-rouge">NameValueSectionHandler</code>.  For example, if we just wanted to add a list of database server urls to use in my app, we could do this in the app.config file like so:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="nt">&lt;configuration&gt;</span>
    <span class="nt">&lt;configSections&gt;</span>
        <span class="nt">&lt;section</span> <span class="na">name=</span><span class="s">"ConnectionManagerDatabaseServers"</span> <span class="na">type=</span><span class="s">"System.Configuration.NameValueSectionHandler"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/configSections&gt;</span>
    <span class="nt">&lt;startup&gt;</span>
        <span class="nt">&lt;supportedRuntime</span> <span class="na">version=</span><span class="s">"v4.0"</span> <span class="na">sku=</span><span class="s">".NETFramework,Version=v4.5"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/startup&gt;</span>
    <span class="nt">&lt;ConnectionManagerDatabaseServers&gt;</span>
        <span class="nt">&lt;add</span> <span class="na">key=</span><span class="s">"localhost"</span> <span class="na">value=</span><span class="s">"localhost"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;add</span> <span class="na">key=</span><span class="s">"Dev"</span> <span class="na">value=</span><span class="s">"Dev.MyDomain.local"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;add</span> <span class="na">key=</span><span class="s">"Test"</span> <span class="na">value=</span><span class="s">"Test.MyDomain.local"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;add</span> <span class="na">key=</span><span class="s">"Live"</span> <span class="na">value=</span><span class="s">"Prod.MyDomain.com"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/ConnectionManagerDatabaseServers&gt;</span>
<span class="nt">&lt;/configuration&gt;</span>
</code></pre></div></div>

<p>And then you can access these values in code like so:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">string</span> <span class="n">devUrl</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">connectionManagerDatabaseServers</span> <span class="p">=</span> <span class="n">ConfigurationManager</span><span class="p">.</span><span class="nf">GetSection</span><span class="p">(</span><span class="s">"ConnectionManagerDatabaseServers"</span><span class="p">)</span> <span class="k">as</span> <span class="n">NameValueCollection</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">connectionManagerDatabaseServers</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">devUrl</span> <span class="p">=</span> <span class="n">connectionManagerDatabaseServers</span><span class="p">[</span><span class="s">"Dev"</span><span class="p">].</span><span class="nf">ToString</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Sometimes though you don’t know what the keys are going to be and you just want to grab all of the values in that <code class="language-plaintext highlighter-rouge">ConnectionManagerDatabaseServers</code> section.  In that case you can get them all like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Grab the Environments listed in the App.config and add them to our list.</span>
<span class="kt">var</span> <span class="n">connectionManagerDatabaseServers</span> <span class="p">=</span> <span class="n">ConfigurationManager</span><span class="p">.</span><span class="nf">GetSection</span><span class="p">(</span><span class="s">"ConnectionManagerDatabaseServers"</span><span class="p">)</span> <span class="k">as</span> <span class="n">NameValueCollection</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">connectionManagerDatabaseServers</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">serverKey</span> <span class="k">in</span> <span class="n">connectionManagerDatabaseServers</span><span class="p">.</span><span class="n">AllKeys</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">string</span> <span class="n">serverValue</span> <span class="p">=</span> <span class="n">connectionManagerDatabaseServers</span><span class="p">.</span><span class="nf">GetValues</span><span class="p">(</span><span class="n">serverKey</span><span class="p">).</span><span class="nf">FirstOrDefault</span><span class="p">();</span>
        <span class="nf">AddDatabaseServer</span><span class="p">(</span><span class="n">serverValue</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here we just assume that the <code class="language-plaintext highlighter-rouge">AddDatabaseServer()</code> function adds the given string to some list of strings.</p>

<p>One thing to note is that in the app.config file, <code class="language-plaintext highlighter-rouge">&lt;configSections&gt;</code> must be the first thing to appear in the <code class="language-plaintext highlighter-rouge">&lt;configuration&gt;</code> section, otherwise an error will be thrown at runtime. Also, the ConfigurationManager class is in the System.Configuration namespace, so be sure you have</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Configuration</span>
</code></pre></div></div>

<p>at the top of your C# files, as well as the <code class="language-plaintext highlighter-rouge">System.Configuration</code> assembly included in your project’s references.</p>

<p>So this works great, but what about when we want to bring in more values than just a single string (or technically you could use this to bring in 2 strings, where the “key” could be the other string you want to store; for example, we could have stored the value of the Key as the user-friendly name of the url).</p>

<h2 id="more-advanced-and-more-complicated">More Advanced (and more complicated)</h2>

<p>So if you want to bring in more information than a string or two per object in the section, then you can no longer simply use the built-in <code class="language-plaintext highlighter-rouge">System.Configuration.NameValueSectionHandler</code> type provided for us.  Instead you have to build your own types.  Here let’s assume that we again want to configure a set of addresses (i.e. urls), but we want to specify some extra info with them, such as the user-friendly name, if they require SSL or not, and a list of security groups that are allowed to save changes made to these endpoints.</p>

<p>So let’s start by looking at the app.config:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="nt">&lt;configuration&gt;</span>
    <span class="nt">&lt;configSections&gt;</span>
        <span class="nt">&lt;section</span> <span class="na">name=</span><span class="s">"ConnectionManagerDataSection"</span> <span class="na">type=</span><span class="s">"ConnectionManagerUpdater.Data.Configuration.ConnectionManagerDataSection, ConnectionManagerUpdater"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/configSections&gt;</span>
    <span class="nt">&lt;startup&gt;</span>
        <span class="nt">&lt;supportedRuntime</span> <span class="na">version=</span><span class="s">"v4.0"</span> <span class="na">sku=</span><span class="s">".NETFramework,Version=v4.5"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;/startup&gt;</span>
    <span class="nt">&lt;ConnectionManagerDataSection&gt;</span>
        <span class="nt">&lt;ConnectionManagerEndpoints&gt;</span>
            <span class="nt">&lt;add</span> <span class="na">name=</span><span class="s">"Development"</span> <span class="na">address=</span><span class="s">"Dev.MyDomain.local"</span> <span class="na">useSSL=</span><span class="s">"false"</span> <span class="nt">/&gt;</span>
            <span class="nt">&lt;add</span> <span class="na">name=</span><span class="s">"Test"</span> <span class="na">address=</span><span class="s">"Test.MyDomain.local"</span> <span class="na">useSSL=</span><span class="s">"true"</span> <span class="nt">/&gt;</span>
            <span class="nt">&lt;add</span> <span class="na">name=</span><span class="s">"Live"</span> <span class="na">address=</span><span class="s">"Prod.MyDomain.com"</span> <span class="na">useSSL=</span><span class="s">"true"</span> <span class="na">securityGroupsAllowedToSaveChanges=</span><span class="s">"ConnectionManagerUsers"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;/ConnectionManagerEndpoints&gt;</span>
    <span class="nt">&lt;/ConnectionManagerDataSection&gt;</span>
<span class="nt">&lt;/configuration&gt;</span>
</code></pre></div></div>

<p>The first thing to notice here is that my section is now using the type <code class="language-plaintext highlighter-rouge">ConnectionManagerUpdater.Data.Configuration.ConnectionManagerDataSection</code> (the fully qualified path to my new class I created) and <code class="language-plaintext highlighter-rouge">ConnectionManagerUpdater</code> (the name of the assembly my new class is in).  Next, you will also notice an extra layer down in the <code class="language-plaintext highlighter-rouge">&lt;ConnectionManagerDataSection&gt;</code> which is the <code class="language-plaintext highlighter-rouge">&lt;ConnectionManagerEndpoints&gt;</code> element.  This is a new collection class that I created to hold each of the Endpoint entries that are defined.  Let’s look at that code now:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Configuration</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Linq</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Threading.Tasks</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">ConnectionManagerUpdater.Data.Configuration</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">class</span> <span class="nc">ConnectionManagerDataSection</span> <span class="p">:</span> <span class="n">ConfigurationSection</span>
    <span class="p">{</span>
        <span class="c1">/// &lt;summary&gt;</span>
        <span class="c1">/// The name of this section in the app.config.</span>
        <span class="c1">/// &lt;/summary&gt;</span>
        <span class="k">public</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">SectionName</span> <span class="p">=</span> <span class="s">"ConnectionManagerDataSection"</span><span class="p">;</span>

        <span class="k">private</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">EndpointCollectionName</span> <span class="p">=</span> <span class="s">"ConnectionManagerEndpoints"</span><span class="p">;</span>

        <span class="p">[</span><span class="nf">ConfigurationProperty</span><span class="p">(</span><span class="n">EndpointCollectionName</span><span class="p">)]</span>
        <span class="p">[</span><span class="nf">ConfigurationCollection</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">ConnectionManagerEndpointsCollection</span><span class="p">),</span> <span class="n">AddItemName</span> <span class="p">=</span> <span class="s">"add"</span><span class="p">)]</span>
        <span class="k">public</span> <span class="n">ConnectionManagerEndpointsCollection</span> <span class="n">ConnectionManagerEndpoints</span> <span class="p">{</span> <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="n">ConnectionManagerEndpointsCollection</span><span class="p">)</span><span class="k">base</span><span class="p">[</span><span class="n">EndpointCollectionName</span><span class="p">];</span> <span class="p">}</span> <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">class</span> <span class="nc">ConnectionManagerEndpointsCollection</span> <span class="p">:</span> <span class="n">ConfigurationElementCollection</span>
    <span class="p">{</span>
        <span class="k">protected</span> <span class="k">override</span> <span class="n">ConfigurationElement</span> <span class="nf">CreateNewElement</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nf">ConnectionManagerEndpointElement</span><span class="p">();</span>
        <span class="p">}</span>

        <span class="k">protected</span> <span class="k">override</span> <span class="kt">object</span> <span class="nf">GetElementKey</span><span class="p">(</span><span class="n">ConfigurationElement</span> <span class="n">element</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">return</span> <span class="p">((</span><span class="n">ConnectionManagerEndpointElement</span><span class="p">)</span><span class="n">element</span><span class="p">).</span><span class="n">Name</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">class</span> <span class="nc">ConnectionManagerEndpointElement</span> <span class="p">:</span> <span class="n">ConfigurationElement</span>
    <span class="p">{</span>
        <span class="p">[</span><span class="nf">ConfigurationProperty</span><span class="p">(</span><span class="s">"name"</span><span class="p">,</span> <span class="n">IsRequired</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span>
        <span class="p">{</span>
            <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="k">this</span><span class="p">[</span><span class="s">"name"</span><span class="p">];</span> <span class="p">}</span>
            <span class="k">set</span> <span class="p">{</span> <span class="k">this</span><span class="p">[</span><span class="s">"name"</span><span class="p">]</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
        <span class="p">}</span>

        <span class="p">[</span><span class="nf">ConfigurationProperty</span><span class="p">(</span><span class="s">"address"</span><span class="p">,</span> <span class="n">IsRequired</span> <span class="p">=</span> <span class="k">true</span><span class="p">)]</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="n">Address</span>
        <span class="p">{</span>
            <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="k">this</span><span class="p">[</span><span class="s">"address"</span><span class="p">];</span> <span class="p">}</span>
            <span class="k">set</span> <span class="p">{</span> <span class="k">this</span><span class="p">[</span><span class="s">"address"</span><span class="p">]</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
        <span class="p">}</span>

        <span class="p">[</span><span class="nf">ConfigurationProperty</span><span class="p">(</span><span class="s">"useSSL"</span><span class="p">,</span> <span class="n">IsRequired</span> <span class="p">=</span> <span class="k">false</span><span class="p">,</span> <span class="n">DefaultValue</span> <span class="p">=</span> <span class="k">false</span><span class="p">)]</span>
        <span class="k">public</span> <span class="kt">bool</span> <span class="n">UseSSL</span>
        <span class="p">{</span>
            <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="kt">bool</span><span class="p">)</span><span class="k">this</span><span class="p">[</span><span class="s">"useSSL"</span><span class="p">];</span> <span class="p">}</span>
            <span class="k">set</span> <span class="p">{</span> <span class="k">this</span><span class="p">[</span><span class="s">"useSSL"</span><span class="p">]</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
        <span class="p">}</span>

        <span class="p">[</span><span class="nf">ConfigurationProperty</span><span class="p">(</span><span class="s">"securityGroupsAllowedToSaveChanges"</span><span class="p">,</span> <span class="n">IsRequired</span> <span class="p">=</span> <span class="k">false</span><span class="p">)]</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="n">SecurityGroupsAllowedToSaveChanges</span>
        <span class="p">{</span>
            <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="kt">string</span><span class="p">)</span><span class="k">this</span><span class="p">[</span><span class="s">"securityGroupsAllowedToSaveChanges"</span><span class="p">];</span> <span class="p">}</span>
            <span class="k">set</span> <span class="p">{</span> <span class="k">this</span><span class="p">[</span><span class="s">"securityGroupsAllowedToSaveChanges"</span><span class="p">]</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span> <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So here the first class we declare is the one that appears in the <code class="language-plaintext highlighter-rouge">&lt;configSections&gt;</code> element of the app.config.  It is <code class="language-plaintext highlighter-rouge">ConnectionManagerDataSection</code> and it inherits from the necessary <code class="language-plaintext highlighter-rouge">System.Configuration.ConfigurationSection class</code>.  This class just has one property (other than the expected section name), that basically just says I have a Collection property, which is actually a <code class="language-plaintext highlighter-rouge">ConnectionManagerEndpointsCollection</code>, which is the next class defined.</p>

<p>The <code class="language-plaintext highlighter-rouge">ConnectionManagerEndpointsCollection</code> class inherits from <code class="language-plaintext highlighter-rouge">ConfigurationElementCollection</code> and overrides the required fields.  The first tells it what type of Element to create when adding a new one (in our case a <code class="language-plaintext highlighter-rouge">ConnectionManagerEndpointElement</code>), and a function specifying what property on our <code class="language-plaintext highlighter-rouge">ConnectionManagerEndpointElement</code> class is the unique key, which I’ve specified to be the Name field.</p>

<p>The last class defined is the actual meat of our elements.  It inherits from <code class="language-plaintext highlighter-rouge">ConfigurationElement</code> and specifies the properties of the element (which can then be set in the xml of the App.config).  The <code class="language-plaintext highlighter-rouge">ConfigurationProperty</code> attribute on each of the properties tells what we expect the name of the property to correspond to in each element in the app.config, as well as some additional information such as if that property is required and what it’s default value should be.</p>

<p>Finally, the code to actually access these values would look like this:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Grab the Environments listed in the App.config and add them to our list.</span>
<span class="kt">var</span> <span class="n">connectionManagerDataSection</span> <span class="p">=</span> <span class="n">ConfigurationManager</span><span class="p">.</span><span class="nf">GetSection</span><span class="p">(</span><span class="n">ConnectionManagerDataSection</span><span class="p">.</span><span class="n">SectionName</span><span class="p">)</span> <span class="k">as</span> <span class="n">ConnectionManagerDataSection</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">connectionManagerDataSection</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">foreach</span> <span class="p">(</span><span class="n">ConnectionManagerEndpointElement</span> <span class="n">endpointElement</span> <span class="k">in</span> <span class="n">connectionManagerDataSection</span><span class="p">.</span><span class="n">ConnectionManagerEndpoints</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">endpoint</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ConnectionManagerEndpoint</span><span class="p">()</span> <span class="p">{</span> <span class="n">Name</span> <span class="p">=</span> <span class="n">endpointElement</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">ServerInfo</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ConnectionManagerServerInfo</span><span class="p">()</span> <span class="p">{</span> <span class="n">Address</span> <span class="p">=</span> <span class="n">endpointElement</span><span class="p">.</span><span class="n">Address</span><span class="p">,</span> <span class="n">UseSSL</span> <span class="p">=</span> <span class="n">endpointElement</span><span class="p">.</span><span class="n">UseSSL</span><span class="p">,</span> <span class="n">SecurityGroupsAllowedToSaveChanges</span> <span class="p">=</span> <span class="n">endpointElement</span><span class="p">.</span><span class="n">SecurityGroupsAllowedToSaveChanges</span><span class="p">.</span><span class="nf">Split</span><span class="p">(</span><span class="sc">','</span><span class="p">).</span><span class="nf">Where</span><span class="p">(</span><span class="n">e</span> <span class="p">=&gt;</span> <span class="p">!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">e</span><span class="p">)).</span><span class="nf">ToList</span><span class="p">()</span> <span class="p">}</span> <span class="p">};</span>
        <span class="nf">AddEndpoint</span><span class="p">(</span><span class="n">endpoint</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This looks very similar to what we had before in the “simple” example.  The main points of interest are that we cast the section as <code class="language-plaintext highlighter-rouge">ConnectionManagerDataSection</code> (which is the class we defined for our section) and then iterate over the endpoints collection using the <code class="language-plaintext highlighter-rouge">ConnectionManagerEndpoints</code> property we created in the <code class="language-plaintext highlighter-rouge">ConnectionManagerDataSection</code> class.</p>

<p>Also, some other helpful resources around using app.config that I found (and for parts that I didn’t really explain in this article) are:</p>

<p><a href="http://stackoverflow.com/questions/4670669/how-do-you-use-sections-in-c-sharp-4-0-app-config">How do you use sections in C# 4.0 app.config?</a> (Stack Overflow) - Shows how to use Section Groups as well, which is something that I did not cover here, but might be of interest to you.</p>

<p><a href="http://msdn.microsoft.com/en-us/library/2tw134k3.aspx">How to: Create Custom Configuration Sections Using Configuration Section</a> (MSDN)</p>

<p><a href="http://msdn.microsoft.com/en-us/library/system.configuration.configurationsection.aspx">ConfigurationSection Class</a> (MSDN)</p>

<p><a href="http://msdn.microsoft.com/en-us/library/system.configuration.configurationcollectionattribute.aspx">ConfigurationCollectionAttribute Class</a> (MSDN)</p>

<p><a href="http://msdn.microsoft.com/en-us/library/system.configuration.configurationelementcollection.aspx">ConfigurationElementCollection Class</a> (MSDN)</p>

<p>I hope you find this helpful.  Feel free to leave a comment.  Happy Coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="DotNet" /><category term="CSharp" /><category term="app.config" /><category term="CSharp" /><category term="configuration" /><category term="CSharp" /><category term="section" /><summary type="html"><![CDATA[Update (Feb 10, 2016): I found a NuGet package called simple-config that allows you to dynamically bind a section in your web/app.config file to a strongly typed class without having to write all of the boiler-plate code that I show here. This may be an easier solution for you than going through the code I show below in this post.]]></summary></entry><entry><title type="html">SQL Server script commands to check if object exists and drop it</title><link href="https://blog.danskingdom.com/sql-server-script-commands-to-check-if-object-exists-and-drop-it/" rel="alternate" type="text/html" title="SQL Server script commands to check if object exists and drop it" /><published>2012-09-14T20:03:00+00:00</published><updated>2019-06-01T00:00:00+00:00</updated><id>https://blog.danskingdom.com/sql-server-script-commands-to-check-if-object-exists-and-drop-it</id><content type="html" xml:base="https://blog.danskingdom.com/sql-server-script-commands-to-check-if-object-exists-and-drop-it/"><![CDATA[<p>Over the past couple years I’ve been keeping track of common SQL Server script commands that I use so I don’t have to constantly Google them. Most of them are how to check if a SQL Server object exists before dropping it. I thought others might find these useful to have them all in one place, so here you go:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">--===============================</span>
<span class="c1">-- Create a new table and add keys and constraints</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLES</span> <span class="k">WHERE</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">TableName</span><span class="p">]</span>
	<span class="p">(</span>
		<span class="p">[</span><span class="n">ColumnName1</span><span class="p">]</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span> <span class="c1">-- To have a field auto-increment add IDENTITY(1,1)</span>
		<span class="p">[</span><span class="n">ColumnName2</span><span class="p">]</span> <span class="nb">INT</span> <span class="k">NULL</span><span class="p">,</span>
		<span class="p">[</span><span class="n">ColumnName3</span><span class="p">]</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span><span class="p">(</span><span class="s1">''</span><span class="p">)</span>
	<span class="p">)</span>

	<span class="c1">-- Add the table's primary key</span>
	<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">TableName</span><span class="p">]</span> <span class="k">ADD</span> <span class="k">CONSTRAINT</span> <span class="p">[</span><span class="n">PK_TableName</span><span class="p">]</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">NONCLUSTERED</span>
	<span class="p">(</span>
		<span class="p">[</span><span class="n">ColumnName1</span><span class="p">],</span>
		<span class="p">[</span><span class="n">ColumnName2</span><span class="p">]</span>
	<span class="p">)</span>

	<span class="c1">-- Add a foreign key constraint</span>
	<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">TableName</span><span class="p">]</span> <span class="k">WITH</span> <span class="k">CHECK</span> <span class="k">ADD</span> <span class="k">CONSTRAINT</span> <span class="p">[</span><span class="n">FK_Name</span><span class="p">]</span> <span class="k">FOREIGN</span> <span class="k">KEY</span>
	<span class="p">(</span>
		<span class="p">[</span><span class="n">ColumnName1</span><span class="p">],</span>
		<span class="p">[</span><span class="n">ColumnName2</span><span class="p">]</span>
	<span class="p">)</span>
	<span class="k">REFERENCES</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">Table2Name</span><span class="p">]</span>
	<span class="p">(</span>
		<span class="p">[</span><span class="n">OtherColumnName1</span><span class="p">],</span>
		<span class="p">[</span><span class="n">OtherColumnName2</span><span class="p">]</span>
	<span class="p">)</span>

	<span class="c1">-- Add indexes on columns that are often used for retrieval</span>
	<span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">IN_ColumnNames</span> <span class="k">ON</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">TableName</span><span class="p">]</span>
	<span class="p">(</span>
		<span class="p">[</span><span class="n">ColumnName2</span><span class="p">],</span>
		<span class="p">[</span><span class="n">ColumnName3</span><span class="p">]</span>
	<span class="p">)</span>

	<span class="c1">-- Add a check constraint</span>
	<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">TableName</span><span class="p">]</span> <span class="k">WITH</span> <span class="k">CHECK</span> <span class="k">ADD</span> <span class="k">CONSTRAINT</span> <span class="p">[</span><span class="n">CH_Name</span><span class="p">]</span> <span class="k">CHECK</span> <span class="p">(([</span><span class="n">ColumnName</span><span class="p">]</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0000</span><span class="p">))</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Add a new column to an existing table</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">COLUMNS</span> <span class="k">where</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
	<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">COLUMN_NAME</span> <span class="o">=</span> <span class="s1">'ColumnName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">TableName</span><span class="p">]</span> <span class="k">ADD</span> <span class="p">[</span><span class="n">ColumnName</span><span class="p">]</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>

	<span class="c1">-- Add a description extended property to the column to specify what its purpose is.</span>
	<span class="k">EXEC</span> <span class="n">sys</span><span class="p">.</span><span class="n">sp_addextendedproperty</span> <span class="o">@</span><span class="n">name</span><span class="o">=</span><span class="n">N</span><span class="s1">'MS_Description'</span><span class="p">,</span>
		<span class="o">@</span><span class="n">value</span> <span class="o">=</span> <span class="n">N</span><span class="s1">'Add column comments here, describing what this column is for.'</span> <span class="p">,</span>
		<span class="o">@</span><span class="n">level0type</span><span class="o">=</span><span class="n">N</span><span class="s1">'SCHEMA'</span><span class="p">,</span><span class="o">@</span><span class="n">level0name</span><span class="o">=</span><span class="n">N</span><span class="s1">'dbo'</span><span class="p">,</span> <span class="o">@</span><span class="n">level1type</span><span class="o">=</span><span class="n">N</span><span class="s1">'TABLE'</span><span class="p">,</span>
		<span class="o">@</span><span class="n">level1name</span> <span class="o">=</span> <span class="n">N</span><span class="s1">'TableName'</span><span class="p">,</span> <span class="o">@</span><span class="n">level2type</span><span class="o">=</span><span class="n">N</span><span class="s1">'COLUMN'</span><span class="p">,</span>
		<span class="o">@</span><span class="n">level2name</span> <span class="o">=</span> <span class="n">N</span><span class="s1">'ColumnName'</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a table</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLES</span> <span class="k">WHERE</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP TABLE [TableName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a view</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">VIEWS</span> <span class="k">WHERE</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'ViewName'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP VIEW [ViewName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a column</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">COLUMNS</span> <span class="k">where</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
	<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">COLUMN_NAME</span> <span class="o">=</span> <span class="s1">'ColumnName'</span><span class="p">)</span>
<span class="k">BEGIN</span>

	<span class="c1">-- If the column has an extended property, drop it first.</span>
	<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">sys</span><span class="p">.</span><span class="n">fn_listExtendedProperty</span><span class="p">(</span><span class="n">N</span><span class="s1">'MS_Description'</span><span class="p">,</span> <span class="n">N</span><span class="s1">'SCHEMA'</span><span class="p">,</span> <span class="n">N</span><span class="s1">'dbo'</span><span class="p">,</span> <span class="n">N</span><span class="s1">'Table'</span><span class="p">,</span>
				<span class="n">N</span><span class="s1">'TableName'</span><span class="p">,</span> <span class="n">N</span><span class="s1">'COLUMN'</span><span class="p">,</span> <span class="n">N</span><span class="s1">'ColumnName'</span><span class="p">))</span>
	<span class="k">BEGIN</span>
		<span class="k">EXEC</span> <span class="n">sys</span><span class="p">.</span><span class="n">sp_dropextendedproperty</span> <span class="o">@</span><span class="n">name</span><span class="o">=</span><span class="n">N</span><span class="s1">'MS_Description'</span><span class="p">,</span>
			<span class="o">@</span><span class="n">level0type</span><span class="o">=</span><span class="n">N</span><span class="s1">'SCHEMA'</span><span class="p">,</span><span class="o">@</span><span class="n">level0name</span><span class="o">=</span><span class="n">N</span><span class="s1">'dbo'</span><span class="p">,</span> <span class="o">@</span><span class="n">level1type</span><span class="o">=</span><span class="n">N</span><span class="s1">'TABLE'</span><span class="p">,</span>
			<span class="o">@</span><span class="n">level1name</span> <span class="o">=</span> <span class="n">N</span><span class="s1">'TableName'</span><span class="p">,</span> <span class="o">@</span><span class="n">level2type</span><span class="o">=</span><span class="n">N</span><span class="s1">'COLUMN'</span><span class="p">,</span>
			<span class="o">@</span><span class="n">level2name</span> <span class="o">=</span> <span class="n">N</span><span class="s1">'ColumnName'</span>
	<span class="k">END</span>

	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'ALTER TABLE [TableName] DROP COLUMN [ColumnName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop Primary key constraint</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLE_CONSTRAINTS</span> <span class="k">WHERE</span> <span class="n">CONSTRAINT_TYPE</span><span class="o">=</span><span class="s1">'PRIMARY KEY'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
		<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">CONSTRAINT_NAME</span> <span class="o">=</span> <span class="s1">'PK_Name'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'ALTER TABLE [TableName] DROP CONSTRAINT [PK_Name]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop Foreign key constraint</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLE_CONSTRAINTS</span> <span class="k">WHERE</span> <span class="n">CONSTRAINT_TYPE</span><span class="o">=</span><span class="s1">'FOREIGN KEY'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
		<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">CONSTRAINT_NAME</span> <span class="o">=</span> <span class="s1">'FK_Name'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'ALTER TABLE [TableName] DROP CONSTRAINT [FK_Name]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop Unique key constraint</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLE_CONSTRAINTS</span> <span class="k">WHERE</span> <span class="n">CONSTRAINT_TYPE</span><span class="o">=</span><span class="s1">'UNIQUE'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
		<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">CONSTRAINT_NAME</span> <span class="o">=</span> <span class="s1">'UNI_Name'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'ALTER TABLE [TableNames] DROP CONSTRAINT [UNI_Name]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop Check constraint</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLE_CONSTRAINTS</span> <span class="k">WHERE</span> <span class="n">CONSTRAINT_TYPE</span><span class="o">=</span><span class="s1">'CHECK'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
		<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">CONSTRAINT_NAME</span> <span class="o">=</span> <span class="s1">'CH_Name'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'ALTER TABLE [TableName] DROP CONSTRAINT [CH_Name]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a column's Default value constraint</span>
<span class="c1">--===============================</span>
<span class="k">DECLARE</span> <span class="o">@</span><span class="n">ConstraintName</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
<span class="k">SET</span> <span class="o">@</span><span class="n">ConstraintName</span> <span class="o">=</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">TOP</span> <span class="mi">1</span> <span class="n">s</span><span class="p">.</span><span class="n">name</span> <span class="k">FROM</span> <span class="n">sys</span><span class="p">.</span><span class="n">sysobjects</span> <span class="n">s</span> <span class="k">JOIN</span> <span class="n">sys</span><span class="p">.</span><span class="n">syscolumns</span> <span class="k">c</span> <span class="k">ON</span> <span class="n">s</span><span class="p">.</span><span class="n">parent_obj</span><span class="o">=</span><span class="k">c</span><span class="p">.</span><span class="n">id</span>
						<span class="k">WHERE</span> <span class="n">s</span><span class="p">.</span><span class="n">xtype</span><span class="o">=</span><span class="s1">'d'</span> <span class="k">AND</span> <span class="k">c</span><span class="p">.</span><span class="n">cdefault</span><span class="o">=</span><span class="n">s</span><span class="p">.</span><span class="n">ID</span>
						<span class="k">AND</span> <span class="n">parent_obj</span> <span class="o">=</span> <span class="n">OBJECT_ID</span><span class="p">(</span><span class="s1">'TableName'</span><span class="p">)</span> <span class="k">AND</span> <span class="k">c</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span><span class="s1">'ColumnName'</span><span class="p">)</span>

<span class="n">IF</span> <span class="o">@</span><span class="n">ConstraintName</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'ALTER TABLE [TableName] DROP CONSTRAINT '</span> <span class="o">+</span> <span class="o">@</span><span class="n">ConstraintName</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Example of how to drop dynamically named Unique constraint</span>
<span class="c1">--===============================</span>
<span class="k">DECLARE</span> <span class="o">@</span><span class="n">ConstraintName</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
<span class="k">SET</span> <span class="o">@</span><span class="n">ConstraintName</span> <span class="o">=</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">TOP</span> <span class="mi">1</span> <span class="k">CONSTRAINT_NAME</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLE_CONSTRAINTS</span>
						<span class="k">WHERE</span> <span class="n">CONSTRAINT_TYPE</span><span class="o">=</span><span class="s1">'UNIQUE'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
						<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">CONSTRAINT_NAME</span> <span class="k">LIKE</span> <span class="s1">'FirstPartOfConstraintName%'</span><span class="p">)</span>

<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">TABLE_CONSTRAINTS</span> <span class="k">WHERE</span> <span class="n">CONSTRAINT_TYPE</span><span class="o">=</span><span class="s1">'UNIQUE'</span> <span class="k">AND</span> <span class="n">TABLE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span>
		<span class="k">AND</span> <span class="k">TABLE_NAME</span> <span class="o">=</span> <span class="s1">'TableName'</span> <span class="k">AND</span> <span class="k">CONSTRAINT_NAME</span> <span class="o">=</span> <span class="o">@</span><span class="n">ConstraintName</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'ALTER TABLE [TableName] DROP CONSTRAINT '</span> <span class="o">+</span> <span class="o">@</span><span class="n">ConstraintName</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Check for and drop a temp table</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="n">OBJECT_ID</span><span class="p">(</span><span class="s1">'tempdb..#TableName'</span><span class="p">)</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DROP</span> <span class="k">TABLE</span> <span class="o">#</span><span class="n">TableName</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a stored procedure</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">ROUTINES</span> <span class="k">WHERE</span> <span class="n">ROUTINE_TYPE</span><span class="o">=</span><span class="s1">'PROCEDURE'</span> <span class="k">AND</span> <span class="k">ROUTINE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span> <span class="k">AND</span>
		<span class="k">ROUTINE_NAME</span> <span class="o">=</span> <span class="s1">'StoredProcedureName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP PROCEDURE [dbo].[StoredProcedureName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a UDF</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">ROUTINES</span> <span class="k">WHERE</span> <span class="n">ROUTINE_TYPE</span><span class="o">=</span><span class="s1">'FUNCTION'</span> <span class="k">AND</span> <span class="k">ROUTINE_SCHEMA</span><span class="o">=</span><span class="s1">'dbo'</span> <span class="k">AND</span>
		<span class="k">ROUTINE_NAME</span> <span class="o">=</span> <span class="s1">'UDFName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP FUNCTION [UDFName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop an Index</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">SYS</span><span class="p">.</span><span class="n">INDEXES</span> <span class="k">WHERE</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'IndexName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP INDEX [IndexName] ON [TableName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a Schema</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">INFORMATION_SCHEMA</span><span class="p">.</span><span class="n">SCHEMATA</span> <span class="k">WHERE</span> <span class="k">SCHEMA_NAME</span> <span class="o">=</span> <span class="s1">'SchemaName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP SCHEMA [SchemaName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a Trigger</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">SYS</span><span class="p">.</span><span class="n">TRIGGERS</span> <span class="k">WHERE</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'TriggerName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP TRIGGER [TriggerName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a custom Type</span>
<span class="c1">--===============================</span>
<span class="k">DECLARE</span> <span class="o">@</span><span class="n">SchemaId</span> <span class="nb">INT</span> <span class="o">=</span> <span class="p">(</span><span class="k">SELECT</span> <span class="n">schema_id</span> <span class="k">FROM</span> <span class="n">sys</span><span class="p">.</span><span class="n">schemas</span> <span class="k">WHERE</span> <span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'dbo'</span><span class="p">)</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">SYS</span><span class="p">.</span><span class="n">TYPES</span> <span class="k">WHERE</span> <span class="n">schema_id</span> <span class="o">=</span> <span class="o">@</span><span class="n">SchemaId</span> <span class="k">AND</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'TypeName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP TYPE [dbo].[TypeName]'</span><span class="p">)</span>
<span class="k">END</span>

<span class="c1">--===============================</span>
<span class="c1">-- Drop a Service Broker Message Type</span>
<span class="c1">--===============================</span>
<span class="n">IF</span> <span class="k">EXISTS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="mi">1</span> <span class="k">FROM</span> <span class="n">SYS</span><span class="p">.</span><span class="n">SERVICE_MESSAGE_TYPES</span> <span class="k">WHERE</span> <span class="n">name</span> <span class="o">=</span> <span class="s1">'MessageTypeName'</span><span class="p">)</span>
<span class="k">BEGIN</span>
	<span class="k">EXEC</span><span class="p">(</span><span class="s1">'DROP TYPE [TypeName]'</span><span class="p">)</span>
<span class="k">END</span>
</code></pre></div></div>

<p><a href="/assets/Posts/2013/09/Common-SQL-Changescript-Code.zip">Download Code Snippet</a></p>

<p>You may have noticed that I wrap the actual <code class="language-plaintext highlighter-rouge">DROP</code> statements in an <code class="language-plaintext highlighter-rouge">EXEC</code> statement. This is because if you run the script once and it drops the schema object, if you try to run the script a second time SQL may complain that the schema object does not exist, and won’t allow you to run the script; sort of like failing a compile-time check. This seems stupid though since we check if the object exists before dropping it, but the “SQL compiler” doesn’t know that. So to avoid this we convert the drop statement to a string and put it in an <code class="language-plaintext highlighter-rouge">EXEC</code>, so that it is not evaluated until “run-time”, and since the <code class="language-plaintext highlighter-rouge">IF EXISTS</code> checks prevent that code from being executed if the schema object does not exist, everything works fine.</p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="SQL" /><category term="Check" /><category term="Drop" /><category term="If Exists" /><category term="Schema" /><category term="SQL" /><category term="SQL Server" /><summary type="html"><![CDATA[Over the past couple years I’ve been keeping track of common SQL Server script commands that I use so I don’t have to constantly Google them. Most of them are how to check if a SQL Server object exists before dropping it. I thought others might find these useful to have them all in one place, so here you go:]]></summary></entry><entry><title type="html">AutoHotkey cannot interact with Windows 8…or can it!</title><link href="https://blog.danskingdom.com/autohotkey-cannot-interact-with-windows-8-windowsor-can-it/" rel="alternate" type="text/html" title="AutoHotkey cannot interact with Windows 8…or can it!" /><published>2012-09-10T19:50:00+00:00</published><updated>2012-09-10T19:50:00+00:00</updated><id>https://blog.danskingdom.com/autohotkey-cannot-interact-with-windows-8-windowsor-can-it</id><content type="html" xml:base="https://blog.danskingdom.com/autohotkey-cannot-interact-with-windows-8-windowsor-can-it/"><![CDATA[<p><strong>Update</strong>: Before you go running your script as an admin, see if <a href="https://blog.danskingdom.com/get-autohotkey-to-interact-with-admin-windows-without-running-ahk-script-as-admin/">this less obtrusive fix</a> will solve your problems.</p>

<p>If you’ve installed Windows 8 and are trying to use AutoHotkey (AHK) to interact with some of the Windows 8 Windows (such as the Control Panel for example), or with apps that need to be Ran As Administrator, then you’ve likely <a href="http://www.autohotkey.com/community/viewtopic.php?f=1&amp;t=92147">become very frustrated as I did</a> to discover that AHK can not send any commands (keyboard or mouse input) to these windows. This was a huge concern as I often need to run Visual Studio as an administrator and wanted my hotkeys and hotstrings to work in Visual Studio. After a day of fighting I finally realized the answer (and it’s pretty obvious once you think about it). If you want AHK to be able to interact with Windows 8 Windows or apps running as administrator, then <strong>you also need to have your AHK script Run As Administrator</strong>.</p>

<p>If you are like me then you probably have your AHK scripts set to run automatically at login, which means you don’t have the opportunity to right-click on the script and manually tell it to Run As Administrator. Luckily the work around is simple.</p>

<p>First, if you want to have your AHK script (or any program for that matter) run when you log in, create a shortcut to the application and place the shortcut in:</p>

<blockquote>
  <p>C:\Users[User Name]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup</p>
</blockquote>

<p>Note that you will need to replace “[User Name]” with your username, and that “AppData” is a hidden folder so you’ll need to turn on viewing hidden folders to see it (you can also type “shell:startup” in the Windows Explorer path to jump directly to this folder). So by placing that shortcut there Windows will automatically run your script when you log on. Now, to get it to run as an administrator by default, right-click on the shortcut and go to Properties. Under the Shortcut tab, click on the “Advanced…” button and check off “Run as administrator”. That’s it. Now when you log onto Windows your script will automatically start up, running as an administrator; allowing it to interact with any application and window like you had expected it to in the first place.</p>

<p>==&lt; EDIT &gt;==</p>

<p>This method works for running AHK scripts that <em>don’t</em> require admin privileges at startup. It only works for running AHK scripts as admin at Windows startup if you have <a href="http://www.eightforums.com/system-security/2434-disable-uac-completely.html">disabled UAC in the registry in Windows 8</a>, which you likely do not want to do (and I had done at the time of writing this article, but have since switched it back on). For a better, UAC-friendly solution to running your AHK scripts as admin at startup, <a href="https://blog.danskingdom.com/get-autohotkey-script-to-run-as-admin-at-startup/">see my newer post</a> to actually get your AHK script to run as admin at startup.</p>

<p>If you do need your AHK script to run as admin and plan on manually double-clicking your AHK script to launch it though, then you can still use this trick of create a shortcut and setting it to Run As Admin in order to avoid having to right-click the AHK script and choose Run As Admin.</p>

<p>==&lt;/ EDIT &gt;==</p>

<p><img src="/assets/Posts/2012/11/image_thumb.png" alt="Shortcut file advanced properties" /></p>

<p>Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="Windows 8" /><category term="AHK" /><category term="AutoHotkey" /><category term="Run As Admin" /><category term="Run at Startup" /><category term="Windows 8" /><summary type="html"><![CDATA[Update: Before you go running your script as an admin, see if this less obtrusive fix will solve your problems.]]></summary></entry><entry><title type="html">XNA BasicEffect takes ~1 second to initialize, AlphaTestEffect ~0.1</title><link href="https://blog.danskingdom.com/xna-basiceffect-takes-1-second-to-initialize-alphatesteffect-0-1/" rel="alternate" type="text/html" title="XNA BasicEffect takes ~1 second to initialize, AlphaTestEffect ~0.1" /><published>2012-08-17T19:47:00+00:00</published><updated>2012-08-17T19:47:00+00:00</updated><id>https://blog.danskingdom.com/xna-basiceffect-takes-1-second-to-initialize-alphatesteffect-0-1</id><content type="html" xml:base="https://blog.danskingdom.com/xna-basiceffect-takes-1-second-to-initialize-alphatesteffect-0-1/"><![CDATA[<p>This is a general message for all XNA developers that in XNA 4 it takes typically 1 - 1.5 seconds to create a new BasicEffect object, while only about 100 milliseconds to create an AlphaTestEffect object. So if your load times are horrible and you use lots of BasicEffect objects, that may be why. I’ve posted this on the <a href="http://xboxforums.create.msdn.com/forums/p/107905/635257.aspx#635257">XNA Community Forums</a> as well; I thought it was worth sharing with other XNA devs ;-)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="XNA" /><category term="AlphaEffect" /><category term="BasicEffect" /><category term="Performance" /><category term="XNA" /><summary type="html"><![CDATA[This is a general message for all XNA developers that in XNA 4 it takes typically 1 - 1.5 seconds to create a new BasicEffect object, while only about 100 milliseconds to create an AlphaTestEffect object. So if your load times are horrible and you use lots of BasicEffect objects, that may be why. I’ve posted this on the XNA Community Forums as well; I thought it was worth sharing with other XNA devs ;-)]]></summary></entry><entry><title type="html">Force ClickOnce applications to automatically update without prompting user - Automatically update MinimumRequiredVersion using PowerShell</title><link href="https://blog.danskingdom.com/force-clickonce-applications-to-automatically-update-without-prompting-user-automatically-update-minimumrequiredversion-using-powershell/" rel="alternate" type="text/html" title="Force ClickOnce applications to automatically update without prompting user - Automatically update MinimumRequiredVersion using PowerShell" /><published>2012-08-15T19:28:00+00:00</published><updated>2012-08-15T19:28:00+00:00</updated><id>https://blog.danskingdom.com/force-clickonce-applications-to-automatically-update-without-prompting-user-automatically-update-minimumrequiredversion-using-powershell</id><content type="html" xml:base="https://blog.danskingdom.com/force-clickonce-applications-to-automatically-update-without-prompting-user-automatically-update-minimumrequiredversion-using-powershell/"><![CDATA[<p>Today I was thinking about using a ClickOnce application in my build process.  The problem is, when using an installed ClickOnce application (as opposed to an online one) if an update to the ClickOnce application is published, the application prompts the user to Accept or Skip downloading and applying the new update.  This would cause a problem for my automated builds as it would end up waiting forever for a user to click Accept.  <a href="http://stackoverflow.com/questions/1638066/clickonce-skip-asking-for-update-or-fail-lauch-if-skip-is-selected">This post lead me to the answer</a>, which is:</p>

<blockquote>
  <p>If your application is an installed application, you can force updates by using the MinimumRequiredVersion attribute. If you publish your application using Visual Studio, you can set this property from the Updates Dialog.</p>
</blockquote>

<p>Just for clarification, the dialog he mentions can be found in Visual Studio in Project Properties-&gt;Publish-&gt;Updates…  Ok, great.  This will allow the prompt to be suppressed, which is also useful if you don’t want to allow users to skip updates.</p>

<p>There is still a problem however.  <strong>Every time I publish a new version of the tool I have to remember to go in and update the MinimumRequiredVersion</strong>.  If I forget to do this and then publish another release, the prompt will be back and will ruin my automated builds.</p>

<p>To get around this I created a PowerShell script that keeps the MinimumRequiredVersion up to date, and I call it from a Post-Build event.  This allows me to never have to worry about manually setting the Minimum Required Version, since it gets updated automatically after every successful build.</p>

<p><strong>Update</strong>: I’ve improved upon the powershell script below and created <a href="https://nuget.org/packages/AutoUpdateProjectsMinimumRequiredClickOnceVersion">a NuGet package that handles all of the setup/installation for you</a>, as described <a href="https://blog.danskingdom.com/force-your-clickonce-app-to-update-without-prompting-user-now-on-nuget/">in my newer post</a>.</p>

<p>Here is the powershell script:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Script finds the current ClickOnce version in a project's .csproj file, and updates the MinimumRequiredVersion to be this same version.</span><span class="w">
</span><span class="c"># This can be used to force a ClickOnce application to update automatically without prompting the user.</span><span class="w">

</span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Position</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="o">=</span><span class="s2">"Comma separated paths of the .csproj files to process"</span><span class="p">)]</span><span class="w">
</span><span class="kr">Param</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$projectFilePaths</span><span class="p">)</span><span class="w">

</span><span class="c"># If a path to a project file was not provided, grab all of the project files in the same directory as this script.</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="p">(</span><span class="nv">$projectFilePaths</span><span class="p">))</span><span class="w">
</span><span class="p">{</span><span class="w">
	</span><span class="c"># Get the directory that this script is in.</span><span class="w">
	</span><span class="nv">$scriptDirectory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Split-Path</span><span class="w"> </span><span class="bp">$MyInvocation</span><span class="o">.</span><span class="nf">MyCommand</span><span class="o">.</span><span class="nf">Path</span><span class="w"> </span><span class="nt">-Parent</span><span class="w">

	</span><span class="c"># Create comma-separated list of project file paths.</span><span class="w">
	</span><span class="n">Get-Item</span><span class="w"> </span><span class="s2">"</span><span class="nv">$scriptDirectory</span><span class="s2">\*.csproj"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kr">foreach</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$projectFilePaths</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">"</span><span class="bp">$_</span><span class="s2">,"</span><span class="p">}</span><span class="w">
	</span><span class="nv">$projectFilePaths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$projectFilePaths</span><span class="o">.</span><span class="nf">TrimEnd</span><span class="p">(</span><span class="s1">','</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Catch any unhandled exceptions, write its error message, and exit the process with a non-zero error code to indicate failure.</span><span class="w">
</span><span class="kr">trap</span><span class="w">
</span><span class="p">{</span><span class="w">
	</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$errorMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="bp">$_</span><span class="w">
	</span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$exitCode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w">

	</span><span class="c"># If this is one of our custom exceptions, strip the error code off of the front.</span><span class="w">
	</span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$errorMessage</span><span class="o">.</span><span class="nf">SubString</span><span class="p">(</span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s2">"\d"</span><span class="p">)</span><span class="w">
	</span><span class="p">{</span><span class="w">
		</span><span class="nv">$exitCode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$errorMessage</span><span class="o">.</span><span class="nf">SubString</span><span class="p">(</span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="nx">1</span><span class="p">)</span><span class="w">
		</span><span class="nv">$errorMessage</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$errorMessage</span><span class="o">.</span><span class="nf">SubString</span><span class="p">(</span><span class="nx">1</span><span class="p">)</span><span class="w">
	</span><span class="p">}</span><span class="w">

	</span><span class="n">Write-Error</span><span class="w"> </span><span class="nv">$errorMessage</span><span class="w">
	</span><span class="kr">EXIT</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$exitCode</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">Function</span><span class="w"> </span><span class="nf">UpdateProjectsMinimumRequiredClickOnceVersion</span><span class="w">
</span><span class="p">{</span><span class="w">
	</span><span class="kr">Param</span><span class="w">
	</span><span class="p">(</span><span class="w">
		</span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="o">=</span><span class="bp">$true</span><span class="p">,</span><span class="w"> </span><span class="n">Position</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">HelpMessage</span><span class="o">=</span><span class="s2">"The project file (.csproj) to update."</span><span class="p">)]</span><span class="w">
		</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$projectFilePath</span><span class="w">
	</span><span class="p">)</span><span class="w">
	</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="p">([</span><span class="n">System.IO.File</span><span class="p">]::</span><span class="n">Exists</span><span class="p">(</span><span class="nv">$projectFilePath</span><span class="p">)))</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"2Cannot find project file to update at the path: '</span><span class="nv">$projectFilePath</span><span class="s2">'"</span><span class="w"> </span><span class="p">}</span><span class="w">

	</span><span class="c"># Build the regular expressions to find the information we will need.</span><span class="w">
	</span><span class="nv">$rxMinimumRequiredVersionTag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Text.RegularExpressions.Regex</span><span class="w"> </span><span class="s2">"\&lt;MinimumRequiredVersion\&gt;(?&lt;Version&gt;.*?)\&lt;/MinimumRequiredVersion\&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nx">SingleLine</span><span class="w">
	</span><span class="nv">$rxApplicationVersionTag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Text.RegularExpressions.Regex</span><span class="w"> </span><span class="s2">"\&lt;ApplicationVersion\&gt;(?&lt;Version&gt;\d+\.\d+\.\d+\.).*?\&lt;/ApplicationVersion\&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nx">SingleLine</span><span class="w">
	</span><span class="nv">$rxApplicationRevisionTag</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Text.RegularExpressions.Regex</span><span class="w"> </span><span class="s2">"\&lt;ApplicationRevision\&gt;(?&lt;Revision&gt;[0-9]+)\&lt;/ApplicationRevision\&gt;"</span><span class="p">,</span><span class="w"> </span><span class="nx">SingleLine</span><span class="w">
	</span><span class="nv">$rxVersionNumber</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">regex</span><span class="p">]</span><span class="w"> </span><span class="s2">"\d+\.\d+\.\d+\.\d+"</span><span class="w">

	</span><span class="c"># Read the file contents in.</span><span class="w">
	</span><span class="nv">$text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.File</span><span class="p">]::</span><span class="n">ReadAllText</span><span class="p">(</span><span class="nv">$projectFilePath</span><span class="p">)</span><span class="w">

	</span><span class="c"># Get the current Minimum Required Version, and the Version that it should be.</span><span class="w">
	</span><span class="nv">$oldMinimumRequiredVersion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$rxMinimumRequiredVersionTag</span><span class="o">.</span><span class="nf">Match</span><span class="p">(</span><span class="nv">$text</span><span class="p">)</span><span class="o">.</span><span class="n">Groups</span><span class="p">[</span><span class="s2">"Version"</span><span class="p">]</span><span class="o">.</span><span class="nf">Value</span><span class="w">
	</span><span class="nv">$majorMinorBuild</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$rxApplicationVersionTag</span><span class="o">.</span><span class="nf">Match</span><span class="p">(</span><span class="nv">$text</span><span class="p">)</span><span class="o">.</span><span class="n">Groups</span><span class="p">[</span><span class="s2">"Version"</span><span class="p">]</span><span class="o">.</span><span class="nf">Value</span><span class="w">
	</span><span class="nv">$revision</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$rxApplicationRevisionTag</span><span class="o">.</span><span class="nf">Match</span><span class="p">(</span><span class="nv">$text</span><span class="p">)</span><span class="o">.</span><span class="n">Groups</span><span class="p">[</span><span class="s2">"Revision"</span><span class="p">]</span><span class="o">.</span><span class="nf">Value</span><span class="w">
	</span><span class="nv">$newMinimumRequiredVersion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$majorMinorBuild</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$revision</span><span class="w">

	</span><span class="c"># If there was a problem constructing the new version number, throw an error.</span><span class="w">
	</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$rxVersionNumber</span><span class="o">.</span><span class="nf">Match</span><span class="p">(</span><span class="nv">$newMinimumRequiredVersion</span><span class="p">)</span><span class="o">.</span><span class="nf">Success</span><span class="p">)</span><span class="w">
	</span><span class="p">{</span><span class="w">
		</span><span class="kr">throw</span><span class="w"> </span><span class="s2">"3'</span><span class="nv">$projectFilePath</span><span class="s2">' does not appear to have any ClickOnce deployment settings in it."</span><span class="w">
	</span><span class="p">}</span><span class="w">

	</span><span class="c"># If we couldn't find the old Minimum Required Version, throw an error.</span><span class="w">
	</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$rxVersionNumber</span><span class="o">.</span><span class="nf">Match</span><span class="p">(</span><span class="nv">$oldMinimumRequiredVersion</span><span class="p">)</span><span class="o">.</span><span class="nf">Success</span><span class="p">)</span><span class="w">
	</span><span class="p">{</span><span class="w">
		</span><span class="kr">throw</span><span class="w"> </span><span class="s2">"4'</span><span class="nv">$projectFilePath</span><span class="s2">' is not currently set to enforce a MinimumRequiredVersion. To fix this in Visual Studio go to Project Properties-&gt;Publish-&gt;Updates... and check off 'Specify a minimum required version for this application'."</span><span class="w">
	</span><span class="p">}</span><span class="w">

	</span><span class="c"># Only write to the file if it is not already up to date.</span><span class="w">
	</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$newMinimumRequiredVersion</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$oldMinimumRequiredVersion</span><span class="p">)</span><span class="w">
	</span><span class="p">{</span><span class="w">
		</span><span class="n">Write</span><span class="w"> </span><span class="s2">"The Minimum Required Version of '</span><span class="nv">$projectFilePath</span><span class="s2">' is already up-to-date on version '</span><span class="nv">$newMinimumRequiredVersion</span><span class="s2">'."</span><span class="w">
	</span><span class="p">}</span><span class="w">
	</span><span class="kr">else</span><span class="w">
	</span><span class="p">{</span><span class="w">
		</span><span class="c"># Update the file contents and write them back to the file.</span><span class="w">
		</span><span class="nv">$text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$rxMinimumRequiredVersionTag</span><span class="o">.</span><span class="nf">Replace</span><span class="p">(</span><span class="nv">$text</span><span class="p">,</span><span class="w"> </span><span class="s2">"&lt;MinimumRequiredVersion&gt;"</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$newMinimumRequiredVersion</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s2">"&lt;/MinimumRequiredVersion&gt;"</span><span class="p">)</span><span class="w">
		</span><span class="p">[</span><span class="n">System.IO.File</span><span class="p">]::</span><span class="n">WriteAllText</span><span class="p">(</span><span class="nv">$projectFilePath</span><span class="p">,</span><span class="w"> </span><span class="nv">$text</span><span class="p">)</span><span class="w">
		</span><span class="n">Write</span><span class="w"> </span><span class="s2">"Updated Minimum Required Version of '</span><span class="nv">$projectFilePath</span><span class="s2">' from '</span><span class="nv">$oldMinimumRequiredVersion</span><span class="s2">' to '</span><span class="nv">$newMinimumRequiredVersion</span><span class="s2">'"</span><span class="w">
	</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Process each of the project files in the comma-separated list.</span><span class="w">
</span><span class="nv">$projectFilePaths</span><span class="o">.</span><span class="nf">Split</span><span class="p">(</span><span class="s2">","</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kr">foreach</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">UpdateProjectsMinimumRequiredClickOnceVersion</span><span class="w"> </span><span class="bp">$_</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The script was actually very small at first, but after commenting it and adding some proper error handling it is fairly large now.</p>

<p>So copy-paste the powershell script text into a new file, such as “UpdateClickOnceVersion.ps1”, and add this file to your project somewhere.</p>

<p>The next step now is to call this script from the Post Build event, so in Visual Studio go into your ClickOnce project’s properties and go to the Build Events tab.  In the Post-build event command line put the following:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>REM Update the ClickOnce MinimumRequiredVersion so that it auto-updates without prompting
PowerShell Set-ExecutionPolicy RemoteSigned
PowerShell <span class="s2">"</span><span class="si">$(</span>ProjectDir<span class="si">)</span><span class="s2">UpdateClickOnceVersion.ps1"</span> <span class="s2">"</span><span class="si">$(</span>ProjectPath<span class="si">)</span><span class="s2">"</span>
</code></pre></div></div>

<p>The first line is just a comment.  The second line may be a security concern, so you might want to remove it.  Basically by default PowerShell is not allowed to run any scripts, so trying to run our script above would result in an error.  To fix this we change the execution policy to allow remote signed scripts to be ran.  I’m not going to pretend to understand why powershell requires this (as I just started learning it today), but that line only needs to be ran on a PC once, so if you want to remove it from here and just run it from PowerShell manually instead (if you haven’t already ran it before), feel free to do so.  I just include it here so that other developers who build this tool in the future don’t have to worry about this setting.</p>

<p>The third line is where we are actually calling the powershell script, passing the path to the .csproj file to update as a parameter.  I added the powershell script to my project at the root level (so it sits right beside the .csproj file), but you can put the powershell script wherever you like.  Also, you don’t even have to include it in the project if you don’t want to, but I chose to so that it is easily visible for other developers when in Visual Studio, and so that it implicitly gets added to source control.  If you want to put the script in a folder instead of in the root of the project directory feel free; just remember to properly update the path in the post-build events.</p>

<p>So after going through and adding all of my nice error messages to the powershell script, I realized that if there is a problem with the script Visual Studio does not forward the error message to the Output window like I hoped it would; it just spits out the text in the Post-build event window and says an error occurred; which doesn’t really tell us anything.  So if you find you are getting errors, copy-paste that third line into PowerShell, replace the macro variables for their absolute values, and run it there.  Powershell should then give you a much more informative error message.</p>

<p>One last comment about this process is that because the powershell script modifies the .csproj outside of Visual Studio, after you publish a new version and build, the script will write to that .csproj file and Visual Studio will give you a prompt that the project was modified outside of Visual Studio and will want to reload it.  You can choose to reload it (which will close all file tabs for that project), or choose to ignore it; it’s up to you.  This is the one minor annoyance I haven’t been able to find a way around, but it’s still better than having to remember to update the Minimum Required Version manually after every new version of the tool I publish.</p>

<p>I hope you find this post useful, and I appreciate any comments; good or bad.  Happy coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="ClickOnce" /><category term="PowerShell" /><category term="Automate" /><category term="ClickOnce" /><category term="PowerShell" /><category term="Visual Studio" /><summary type="html"><![CDATA[Today I was thinking about using a ClickOnce application in my build process.  The problem is, when using an installed ClickOnce application (as opposed to an online one) if an update to the ClickOnce application is published, the application prompts the user to Accept or Skip downloading and applying the new update.  This would cause a problem for my automated builds as it would end up waiting forever for a user to click Accept.  This post lead me to the answer, which is:]]></summary></entry><entry><title type="html">VS 11 Beta merge tool is awesome, except for resolving conflicts</title><link href="https://blog.danskingdom.com/vs-11-beta-merge-tool-is-awesome-except-for-resolving-conflicts/" rel="alternate" type="text/html" title="VS 11 Beta merge tool is awesome, except for resolving conflicts" /><published>2012-04-06T05:48:00+00:00</published><updated>2012-04-06T05:48:00+00:00</updated><id>https://blog.danskingdom.com/vs-11-beta-merge-tool-is-awesome-except-for-resolving-conflicts</id><content type="html" xml:base="https://blog.danskingdom.com/vs-11-beta-merge-tool-is-awesome-except-for-resolving-conflicts/"><![CDATA[<p>If you’ve downloaded the new VS 11 Beta and done any merging, then you’ve probably seen the new diff and merge tools built into VS 11. They are awesome, and by far a vast improvement over the ones included in VS 2010. There is one problem with the merge tool though, and in my opinion it is huge.</p>

<p>Basically the problem with the new VS 11 Beta merge tool is that when you are resolving conflicts after performing a merge, you cannot tell what changes were made in each file where the code is conflicting. Was the conflicting code added, deleted, or modified in the source and target branches? I don’t know (without explicitly opening up the history of both the source and target files), and the merge tool doesn’t tell me. In my opinion this is a huge fail on the part of the designers/developers of the merge tool, as it actually forces me to either spend an extra minute for every conflict to view the source and target file history, or to go back to use the merge tool in VS 2010 to properly assess which changes I should take.</p>

<p>I submitted this as a bug to Microsoft, but they say that this is intentional by design. WHAT?! So they purposely crippled their tool in order to make it pretty and keep the look consistent with the new diff tool? That’s like purposely putting a little hole in the bottom of your cup for design reasons to make it look cool. Sure, the cup looks cool, but I’m not going to use it if it leaks all over the place and doesn’t do the job that it is intended for. Bah! but I digress.</p>

<p>Because this bug is apparently a feature, they asked me to open up a “feature request” to have the problem fixed. Please go vote up both my <a href="https://connect.microsoft.com/VisualStudio/feedback/details/734678/tfs-11-beta-merge-tool-code-change-conflicts-are-not-clear">bug submission</a> and the <a href="http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2741136-change-vs-11-merge-tool-conflict-coloring-to-conve">feature request</a> so that this tool will actually be useful by the time the final VS 11 product is released.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio" /><category term="merge" /><category term="merge tool" /><category term="Visual Studio" /><category term="Visual Studio 2012" /><category term="VS" /><category term="VS 11" /><summary type="html"><![CDATA[If you’ve downloaded the new VS 11 Beta and done any merging, then you’ve probably seen the new diff and merge tools built into VS 11. They are awesome, and by far a vast improvement over the ones included in VS 2010. There is one problem with the merge tool though, and in my opinion it is huge.]]></summary></entry><entry><title type="html">Parallel MSBuild FTW - Build faster in parallel</title><link href="https://blog.danskingdom.com/parallel-msbuild-ftw-build-faster-in-parallel/" rel="alternate" type="text/html" title="Parallel MSBuild FTW - Build faster in parallel" /><published>2012-03-31T08:09:00+00:00</published><updated>2012-03-31T08:09:00+00:00</updated><id>https://blog.danskingdom.com/parallel-msbuild-ftw-build-faster-in-parallel</id><content type="html" xml:base="https://blog.danskingdom.com/parallel-msbuild-ftw-build-faster-in-parallel/"><![CDATA[<p>Hey everyone, I just discovered <a href="http://www.hanselman.com/blog/FasterBuildsWithMSBuildUsingParallelBuildsAndMulticoreCPUs.aspx">this great post</a> yesterday that shows how to have msbuild build projects in parallel :-).</p>

<p>Basically all you need to do is pass the switches <code class="language-plaintext highlighter-rouge">/m:[NumOfCPUsToUse] /p:BuildInParallel=true</code> into MSBuild.</p>

<p>Example to use 4 cores/processes (If you just pass in <code class="language-plaintext highlighter-rouge">/m</code> it will use all CPU cores):</p>

<blockquote>
  <p>MSBuild /m:4 /p:BuildInParallel=true “C:devClient.sln”</p>
</blockquote>

<p>Obviously this trick will only be useful on PCs with multi-core CPUs (which we should all have by now) and solutions with multiple projects; So there’s no point using it for solutions that only contain one project. Also, testing shows that using multiple processes does not speed up Visual Studio Database deployments either in case you’re curious ;-).</p>

<p>Also, I found that if I didn’t explicitly use <code class="language-plaintext highlighter-rouge">/p:BuildInParallel=true</code> I would get many build errors (even though the <a href="http://msdn.microsoft.com/en-us/library/bb651793.aspx">MSDN documentation</a> says that it is true by default).</p>

<p>The poster boasts compile time improvements up to 59%, but the performance boost you see will vary depending on the solution and its project dependencies. I tested with building a solution at my office, and here are my results (runs are in seconds):</p>

<table>
  <thead>
    <tr>
      <th># of Processes</th>
      <th>1st Run</th>
      <th>2nd Run</th>
      <th>3rd Run</th>
      <th>Avg</th>
      <th style="text-align: right">Performance</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>192</td>
      <td>195</td>
      <td>200</td>
      <td>195.67</td>
      <td style="text-align: right">100%</td>
    </tr>
    <tr>
      <td>2</td>
      <td>155</td>
      <td>156</td>
      <td>156</td>
      <td>155.67</td>
      <td style="text-align: right">79.56%</td>
    </tr>
    <tr>
      <td>4</td>
      <td>146</td>
      <td>149</td>
      <td>146</td>
      <td>147.00</td>
      <td style="text-align: right">75.13%</td>
    </tr>
    <tr>
      <td>8</td>
      <td>136</td>
      <td>136</td>
      <td>138</td>
      <td>136.67</td>
      <td style="text-align: right">69.85%</td>
    </tr>
  </tbody>
</table>

<p>So I updated all of our build scripts to build using 2 cores (~20% speed boost), since that gives us the biggest bang for our buck on our solution without bogging down a machine, and developers may sometimes compile more than 1 solution at a time. I’ve put the any-PC-safe batch script code at the bottom of this post.</p>

<p>The poster also has <a href="http://www.hanselman.com/blog/HackParallelMSBuildsFromWithinTheVisualStudioIDE.aspx">a follow-up post</a> showing how to add a button and keyboard shortcut to the Visual Studio IDE to have VS build in parallel as well (so you don’t <strong>have</strong> to use a build script); if you do this make sure you use the .Net 4.0 MSBuild, not the 3.5 one that he shows in the screenshot. While this did work for me, I found it left an MSBuild.exe process always hanging around afterwards for some reason, so watch out (batch file doesn’t have this problem though). Also, you do get build output, but it may not be the same that you’re used to, and it doesn’t say “Build succeeded” in the status bar when completed, so I chose to not make this my default Visual Studio build option, but you may still want to.</p>

<p>Happy building!</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:: Calculate how many Processes to use to <span class="k">do </span>the build.
SET <span class="nv">NumberOfProcessesToUseForBuild</span><span class="o">=</span>1
SET <span class="nv">BuildInParallel</span><span class="o">=</span><span class="nb">false
</span><span class="k">if</span> %NUMBER_OF_PROCESSORS% GTR 2 <span class="o">(</span>
                SET <span class="nv">NumberOfProcessesToUseForBuild</span><span class="o">=</span>2
                SET <span class="nv">BuildInParallel</span><span class="o">=</span><span class="nb">true</span>
<span class="o">)</span>
MSBuild /maxcpucount:%NumberOfProcessesToUseForBuild% /p:BuildInParallel<span class="o">=</span>%BuildInParallel% <span class="s2">"C:</span><span class="se">\d</span><span class="s2">ev</span><span class="se">\C</span><span class="s2">lient.sln"</span>
</code></pre></div></div>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="batch file" /><category term="Build" /><category term="MSBuild" /><category term="multiple core" /><category term="parallel" /><summary type="html"><![CDATA[Hey everyone, I just discovered this great post yesterday that shows how to have msbuild build projects in parallel :-).]]></summary></entry><entry><title type="html">Path Too Long for Team Foundation Database Project Build</title><link href="https://blog.danskingdom.com/path-too-long-for-team-foundation-database-project-build/" rel="alternate" type="text/html" title="Path Too Long for Team Foundation Database Project Build" /><published>2012-02-06T05:46:00+00:00</published><updated>2012-02-06T05:46:00+00:00</updated><id>https://blog.danskingdom.com/path-too-long-for-team-foundation-database-project-build</id><content type="html" xml:base="https://blog.danskingdom.com/path-too-long-for-team-foundation-database-project-build/"><![CDATA[<p>Arrggghhhh TFS and builds! Such a love-hate relationship! So we have our TFS builds setup to both compile our C# projects as well as compile and deploy our Team Foundation (TF) Database (DB) projects. One day I started getting the following file path too long error message on our build server:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$/RQ4TeamProject/Prototypes/BuildProcessTests/RQ4.Database.sln - 1 error(s), 69 warning(s), View Log File
C:Program Files (x86)MSBuildMicrosoftVisualStudiov10.0TeamDataMicrosoft.Data.Schema.TSqlTasks.targets (80): The "SqlSetupDeployTask" task failed unexpectedly. Microsoft.Data.Schema.Build.BuildFailedException: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters. &amp;#8212;&gt; System.IO.PathTooLongException: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters. at System.IO.PathHelper.Append(Char value) at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength) at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy) at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options) at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize) at System.IO.StreamReader..ctor(String path, Boolean detectEncodingFromByteOrderMarks) at Microsoft.Data.Schema.Sql.Build.SqlPrePostDeploymentModifier.GenerateMergedSqlCmdFiles(DeploymentContributorConfigurationSetup setup, DeploymentContributorConfigurationFile configFile) at Microsoft.Data.Schema.Sql.Build.SqlPrePostDeploymentModifier.OnEstablishDeploymentConfiguration(DeploymentContributorConfigurationSetup setup) at Microsoft.Data.Schema.Build.DeploymentContributor.EstablishDeploymentConfiguration(DeploymentContributorConfigurationSetup setup) &amp;#8212; End of inner exception stack trace &amp;#8212; at Microsoft.Data.Schema.Build.DeploymentContributor.EstablishDeploymentConfiguration(DeploymentContributorConfigurationSetup setup) at Microsoft.Data.Schema.Build.DeploymentProjectBuilder.VerifyConfiguration() at Microsoft.Data.Schema.Tasks.DBSetupDeployTask.BuildDeploymentProject(ErrorManager errors, ExtensionManager em) at Microsoft.Data.Schema.Tasks.DBSetupDeployTask.Execute() at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute() at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Boolean&amp; taskResult)
</code></pre></div></div>

<p>Naturally I said, “Ok, our TF DB project isn’t compiling because a path is too long. Somebody must have checked in a stored procedure with a really long name”. After viewing the history of the branch I was trying to build however, I didn’t see anything that stuck out. So for fun I thought I would shorten the Build Definition name’s length and build again. Viola, like most path issues with TFS this fixed the issue (this is because the build definition name is often used in the path that TFS moves/builds files to). However, we have many queries setup that match the specific Build Definition name (since it’s used in the “Integrated in Build” work item value), so shortening it wasn’t a long term solution. As an added frustration bonus, I discovered our build definition name was only 1 character too long!</p>

<p>The first thing I did was make a <a href="http://pathlengthchecker.codeplex.com">Path Length Checker</a> program so I could see how long the file paths (files and directories) really were on the build server. Oddly enough, the longest paths were 40 characters short of the maximum limit described by the error message.</p>

<p>So I took a look at our database folder structure and saw that it really was wasting a lot of characters. This is what the path to one of our stored procedure folders looks like: “..\DatabaseSchema Objects\Schemas\dbo\Programmability\Stored Procedures\Procs1”. I figured that I would just rename some of these folders in Visual Studio and that should be good……..OMG never try this while connected to TFS! I got a popup warning for every single file under the directory I was renaming (thousands of them), with something along the lines of “Cannot access file X, or it is locked…..blah blah. Please press OK”. So after holding down the enter key for a over an hour to get past all these prompts it finally finished. When I reviewed the changes to check in, I saw that many duplicate folders had been created, and there were miscellaneous files all over the place; some got moved, some never; what a mess. So I went ahead and reverted my changes.</p>

<p>So I thought, “Ok, let’s try this again, but first going offline so as not to connect to TFS”. So I disabled my internet connection and opened the database solution (this is the only way that I know of to work “offline” in TFS :( ) I then tried to change the high level folder “Schema Objects” to just “Schema”. Nope, Visual Studio complained that the folder was locked and couldn’t be changed. I thought to myself, “TFS makes all non-checked out files read-only, and I’m offline so it can’t check them out. That must be the problem”. So I opened up explorer and made all of the files and folders writable and tried again. Nope, no deal; same error message.</p>

<p>So I thought, “Alright, let’s try doing a low level directory instead”. It seems that VS would only let me rename a directory that didn’t contain other directories. So I renamed the “Procs1” folder to just “1”. I no longer got the warning prompt for every file, but it was still pretty slow and I could watch VS process every file in the Solution Explorer window. After about 10 minutes it finally finished. So I checked in my changes and tried building again. Nope, same error message as before about the path being too long.</p>

<p>So I said screw this. I opened up the TFS Source Control Explorer and renamed the folder from there. It worked just fine. I then had to open up the Database.dbproj file in a text editor and do a find and replace to replace “Schema Objects” with “Schema”. This worked for refactoring the folder structure quickly, but I was still getting the “path too long” error message on the build server. Arrrrgg!</p>

<p>So I went back to the build, set the verbosity to “diagnostic” and launched another build (which failed again with the path too long error). Looking through the error message I noticed that it did complete building the DB schema, and went on to failing on building the Pre/Post deployment scripts. Looking back to my original error message and reading it more carefully I noticed this line, “Microsoft.Data.Schema.Sql.Build.SqlPrePostDeploymentModifier.GenerateMergedSqlCmdFiles”. So now I was pretty sure the problem was in the pre and post deployment scripts.</p>

<p>Now, we have a very custom process for our database scripts, and part of this process involves using SQLCMD mode to include other script files into our pre and post deployment files when they are generated; it basically makes it look like the referenced script’s contents were in the pre/post deployment script the entire time. This is necessary for us so that developers don’t have to look through pre and post deployment scripts that are tens of thousands of lines long. It turns out that while none of these referenced script files themselves had a path that was over the limit, somehow during the generation of the pre/post deployment scripts it was making the path even longer. I looked through our referenced scripts and saw a few particularly long ones. So I refactored them to shorten the file names, and presto the build worked! Hooray!</p>

<p>I’m guessing that the reason the build wouldn’t give me an actual filename when it encountered the error is because SQLCMD mode was dynamically referencing those scripts at build time, so to the build it just looked like the pre and post deployment scripts were each thousands of lines long, when in fact they are only maybe 50 lines long, but they “include” other files, and those file references must be used at build time.</p>

<p>So the morals of this story are:</p>

<ol>
  <li>If VS is blowing chunks when you try to rename a folder (especially when connected to TFS), don’t do it through VS. Instead modify the folder structure outside of VS and then manually edit the .csproj/.dbproj/.vbproj files to mirror the changes.</li>
  <li>Whenever you are stumped on a build error, go back and THOROUGHLY read the ENTIRE error message.</li>
  <li>Be careful when using compile-time language features to reference/include external files.</li>
</ol>]]></content><author><name>Daniel Schroeder</name></author><category term="Database" /><category term="TFS" /><category term="Visual Studio" /><category term="error" /><category term="path too long" /><category term="SQLCMD" /><category term="Team Foundation Database" /><category term="TFS" /><category term="Visual Studio" /><summary type="html"><![CDATA[Arrggghhhh TFS and builds! Such a love-hate relationship! So we have our TFS builds setup to both compile our C# projects as well as compile and deploy our Team Foundation (TF) Database (DB) projects. One day I started getting the following file path too long error message on our build server:]]></summary></entry><entry><title type="html">Awesome Visual Studio Videos</title><link href="https://blog.danskingdom.com/awesome-visual-studio-videos/" rel="alternate" type="text/html" title="Awesome Visual Studio Videos" /><published>2011-09-08T05:39:00+00:00</published><updated>2011-09-08T05:39:00+00:00</updated><id>https://blog.danskingdom.com/awesome-visual-studio-videos</id><content type="html" xml:base="https://blog.danskingdom.com/awesome-visual-studio-videos/"><![CDATA[<p>I watched an awesome Channel 9 <a href="http://channel9.msdn.com/Events/TechEd/NorthAmerica/2011/DEV305">Visual Studio Tips And Tricks video</a>, so I thought I’d share. Some great VS gems in there. Even a few Windows 7 ones, like using <kbd>Alt</kbd> + <kbd>D</kbd> to jump to the address bar in Windows Explorer, and typing “cmd” in the Windows Explorer address bar to open a command prompt at the current directory.</p>

<p>Also, if you’re interested in what the next Visual Studio release has in store, <a href="http://channel9.msdn.com/Events/TechEd/NorthAmerica/2011/DEV326">check out this Channel 9 video</a>.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio" /><category term="tips and tricks" /><category term="Visual Studio" /><summary type="html"><![CDATA[I watched an awesome Channel 9 Visual Studio Tips And Tricks video, so I thought I’d share. Some great VS gems in there. Even a few Windows 7 ones, like using Alt + D to jump to the address bar in Windows Explorer, and typing “cmd” in the Windows Explorer address bar to open a command prompt at the current directory.]]></summary></entry><entry><title type="html">Sweet C# Gems</title><link href="https://blog.danskingdom.com/sweet-c-gems/" rel="alternate" type="text/html" title="Sweet C# Gems" /><published>2011-09-03T05:38:00+00:00</published><updated>2011-09-03T05:38:00+00:00</updated><id>https://blog.danskingdom.com/sweet-c-gems</id><content type="html" xml:base="https://blog.danskingdom.com/sweet-c-gems/"><![CDATA[<p><a href="http://geekswithblogs.net/BlackRabbitCoder/Default.aspx">James Michael Hare</a> has a lot of awesome C# and .Net related posts, so this is just a little shout out to him (and my own little way of bookmarking his blogs since GWB doesn’t provide a way to “favourite” or “follow” him). Of particular awesomeness is his <a href="http://geekswithblogs.net/BlackRabbitCoder/archive/2011/08/11/c.net-little-wonders--pitfalls-the-complete-collection-so-far.aspx">C#/.NET Little Wonders and Pitfalls</a> series, and the <a href="http://geekswithblogs.net/BlackRabbitCoder/archive/2011/06/16/c.net-fundamentals-choosing-the-right-collection-class.aspx">C#/.NET Fundamentals: Choosing the Right Collection Class</a>. Keep the great posts coming James!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="DotNet" /><category term="CSharp" /><category term="DotNet" /><category term="CSharp" /><summary type="html"><![CDATA[James Michael Hare has a lot of awesome C# and .Net related posts, so this is just a little shout out to him (and my own little way of bookmarking his blogs since GWB doesn’t provide a way to “favourite” or “follow” him). Of particular awesomeness is his C#/.NET Little Wonders and Pitfalls series, and the C#/.NET Fundamentals: Choosing the Right Collection Class. Keep the great posts coming James!]]></summary></entry><entry><title type="html">Using TFS Programmatically</title><link href="https://blog.danskingdom.com/using-tfs-programmatically/" rel="alternate" type="text/html" title="Using TFS Programmatically" /><published>2011-08-18T05:37:00+00:00</published><updated>2011-08-18T05:37:00+00:00</updated><id>https://blog.danskingdom.com/using-tfs-programmatically</id><content type="html" xml:base="https://blog.danskingdom.com/using-tfs-programmatically/"><![CDATA[<p>I recently discovered <a href="http://blogs.msdn.com/b/jongallant/archive/2011/07/18/how-to-programmatically-modify-a-tfs-query-with-c.aspx">this post</a> which shows how you can programmatically update queries in TFS, which is great for when you make a WIT (Work Item Template) change that will affect tons of queries.</p>

<p>That post then led me <a href="http://geekswithblogs.net/TarunArora/archive/2011/07/10/tfs-2010-sdk-get-projects-iterations-area-path-queries-and.aspx">to this one</a> which is great as well. In fact, <a href="http://geekswithblogs.net/TarunArora/Default.aspx">Tarun Arora</a> has tons of great posts relating to TFS. Rock on!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="TFS" /><category term="query" /><category term="TFS" /><category term="WIT" /><summary type="html"><![CDATA[I recently discovered this post which shows how you can programmatically update queries in TFS, which is great for when you make a WIT (Work Item Template) change that will affect tons of queries.]]></summary></entry><entry><title type="html">TFS Power Tools Features And How To Run Them From Visual Studio</title><link href="https://blog.danskingdom.com/tfs-power-tools-features-and-how-to-run-them-from-visual-studio/" rel="alternate" type="text/html" title="TFS Power Tools Features And How To Run Them From Visual Studio" /><published>2011-07-12T05:35:00+00:00</published><updated>2011-07-12T05:35:00+00:00</updated><id>https://blog.danskingdom.com/tfs-power-tools-features-and-how-to-run-them-from-visual-studio</id><content type="html" xml:base="https://blog.danskingdom.com/tfs-power-tools-features-and-how-to-run-them-from-visual-studio/"><![CDATA[<p>There are a few great features in the <a href="http://visualstudiogallery.msdn.microsoft.com/c255a1e4-04ba-4f68-8f4e-cd473d6b971f">TFS Power Tools</a> that I wasn’t aware of, such as TreeDiff which let’s you compare folders (or even entire branches) to see the differences between them, and Undo Unchanged which undoes files that are checked out, but don’t have any changes made to them, so that they don’t appear in the Pending Changes window. <a href="http://duncanjasmith.blogspot.com/2007/05/using-team-foundation-server-power.html">This blog</a> explains some of the features, and <a href="http://www.aaubry.net/undo-checkout-on-unchanged-files-%28tfs%29.aspx">this one</a> shows how to add the console-app-only features as External Tools in Visual Studio to easily run them from Visual Studio. Very cool!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="TFS" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="TFS" /><category term="TFS Power Tools" /><category term="TreeDiff" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><summary type="html"><![CDATA[There are a few great features in the TFS Power Tools that I wasn’t aware of, such as TreeDiff which let’s you compare folders (or even entire branches) to see the differences between them, and Undo Unchanged which undoes files that are checked out, but don’t have any changes made to them, so that they don’t appear in the Pending Changes window. This blog explains some of the features, and this one shows how to add the console-app-only features as External Tools in Visual Studio to easily run them from Visual Studio. Very cool!]]></summary></entry><entry><title type="html">Visual Studio Tips and Tricks</title><link href="https://blog.danskingdom.com/visual-studio-tips-and-tricks/" rel="alternate" type="text/html" title="Visual Studio Tips and Tricks" /><published>2011-06-30T05:34:00+00:00</published><updated>2011-06-30T05:34:00+00:00</updated><id>https://blog.danskingdom.com/visual-studio-tips-and-tricks</id><content type="html" xml:base="https://blog.danskingdom.com/visual-studio-tips-and-tricks/"><![CDATA[<p>Just found a few websites that show some Visual Studio tips that I haven’t seen before, so I thought I’d share:</p>

<ul>
  <li><a href="http://www.codeproject.com/KB/tips/VSnetIDETipsAndTricks.aspx">Tips and Tricks for the Visual Studio .NET IDE</a></li>
  <li><a href="http://stephenwalther.com/blog/archive/2008/10/21/essential-visual-studio-tips-amp-tricks-that-every-developer-should-know.aspx">Essential Visual Studio Tips &amp; Tricks that Every Developer Should Know</a></li>
  <li><a href="http://channel9.msdn.com/Shows/Visual-Studio-Toolbox">Channel 9’s Visual Studio Toolbox</a> - weekly series dedicated to showing all the cool stuff that Visual Studio can do and how to be more productive with it.</li>
</ul>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="Visual Studio" /><category term="tips and tricks" /><category term="Visual Studio" /><summary type="html"><![CDATA[Just found a few websites that show some Visual Studio tips that I haven’t seen before, so I thought I’d share:]]></summary></entry><entry><title type="html">Making solutions with lots of projects load and build faster in Visual Studio</title><link href="https://blog.danskingdom.com/making-solutions-with-lots-of-projects-load-and-build-faster-in-visual-studio/" rel="alternate" type="text/html" title="Making solutions with lots of projects load and build faster in Visual Studio" /><published>2011-06-02T05:32:00+00:00</published><updated>2011-06-02T05:32:00+00:00</updated><id>https://blog.danskingdom.com/making-solutions-with-lots-of-projects-load-and-build-faster-in-visual-studio</id><content type="html" xml:base="https://blog.danskingdom.com/making-solutions-with-lots-of-projects-load-and-build-faster-in-visual-studio/"><![CDATA[<p>I came across <a href="http://blogs.msdn.com/b/jjameson/archive/2009/03/06/large-visual-studio-solutions-by-loading-unloading-projects.aspx">this great article</a> which talks about simply unloading projects from a solution to make the solution load and build faster in Visual Studio. This is great, as some of the solution files I work in contain over 300 projects and it can sometimes take a while to load. Also, because the information about which projects to load is stored in the .suo file (not the .sln file itself), this can be configured per developer so they only have to load the projects that they work in (the .suo files should never be stored in source control).</p>

<p>Now, unloading 300 projects one at a time would take forever, but luckily there the Visual Studio 2010 extension <a href="http://visualstudiogallery.msdn.microsoft.com/e5f41ad9-4edc-4912-bca3-91147db95b99/">PowerCommands</a> allows us to quickly Load or Unload all of the projects in the solution (or a solution folder) with a couple clicks. So I typically just unload all of the projects in the solution, and then enable the ones I am working with.</p>

<p><img src="/assets/Posts/2012/11/unload-projects-from-solution.png" alt="Unload Projects From Solution" /></p>

<p><strong>The one caveat with this</strong> is that because Visual Studio only builds the projects that are loaded, if you get your team’s latest code from source control and then try to build the solution from within Visual Studio, <strong>the build may fail</strong> since it will only build the projects you have loaded, and these may depend on changes made to the other projects that you don’t have loaded. So you can easily reload all of the projects in the solution, build, and then unload all the ones you don’t want again, or what I prefer to do is simply <a href="http://geekswithblogs.net/deadlydog/archive/2011/06/01/setting-up-keyboard-shortcut-to-build-solution-in-msbuild.aspx">build the solution from MSBuild</a>, as this ignores the .suo file and builds all projects referenced in the .sln file. I often build my solution files from MSBuild anyways since it has the added benefit of not locking up my Visual Studio UI while building :).</p>

<p>Happy Coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Productivity" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="project" /><category term="sln" /><category term="solution" /><category term="unload" /><category term="Visual Studio" /><summary type="html"><![CDATA[I came across this great article which talks about simply unloading projects from a solution to make the solution load and build faster in Visual Studio. This is great, as some of the solution files I work in contain over 300 projects and it can sometimes take a while to load. Also, because the information about which projects to load is stored in the .suo file (not the .sln file itself), this can be configured per developer so they only have to load the projects that they work in (the .suo files should never be stored in source control).]]></summary></entry><entry><title type="html">Setting up keyboard shortcuts to build solutions in MSBuild</title><link href="https://blog.danskingdom.com/setting-up-keyboard-shortcuts-to-build-solutions-in-msbuild/" rel="alternate" type="text/html" title="Setting up keyboard shortcuts to build solutions in MSBuild" /><published>2011-06-01T19:32:00+00:00</published><updated>2011-06-01T19:32:00+00:00</updated><id>https://blog.danskingdom.com/setting-up-keyboard-shortcuts-to-build-solutions-in-msbuild</id><content type="html" xml:base="https://blog.danskingdom.com/setting-up-keyboard-shortcuts-to-build-solutions-in-msbuild/"><![CDATA[<p>One of the greatest benefits of building your solution flies in MSBuild (vs in Visual Studio directly) is that it doesn’t lock up the Visual Studio UI, which can be a huge pain if you have a large solution that takes several minutes (or longer) to build. Building your solution in MSBuild leaves you free to inspect code in Visual Studio while your solution is building. The only problem is that to do this you have to open a command prompt and type the command + path every time to build.</p>

<p>If you want to be able to right-click on a solution file and build it in MSBuild from the Windows Explorer context menu, check out <a href="http://msbuildshellex.codeplex.com/">MSBuildShellExtension</a> (it’s free). Being able to build right from Windows Explorer (without having to even open Visual Studio) is cool and may be enough to pacify you, but I wanted to be able to build my solution file at anytime from anywhere on my PC with a keyboard shortcut.</p>

<p>Below I outline how I’ve setup my system to build my solution files in MSBuild with a quick keystroke. Setup only takes a few minutes and requires <a href="http://www.autohotkey.com/">AutoHotkey</a> to be installed (it’s free and awesome).</p>

<h2 id="step-1">Step 1</h2>

<p>Install <a href="http://www.autohotkey.com/">AutoHotkey</a>.</p>

<h2 id="step-2">Step 2</h2>

<p>Create a shortcut to the Visual Studio Command Prompt (2010), move it directly to the C: drive, and make sure it is called “Visual Studio Command Prompt (2010)” (it is referenced at this location with this name by the AutoHotkey script in the following steps, but can be changed if needed).</p>

<p><img src="/assets/Posts/2012/11/vs2010commandprompt.png" alt="VS2010CommandPrompt" /></p>

<p><img src="/assets/Posts/2012/11/cdrive.png" alt="CDrive" /></p>

<h2 id="step-3">Step 3</h2>

<p>Create your AutoHotkey script…luckily, you don’t have to create it from scratch; you can use mine as your template and just adjust it to your liking :-) . So copy and paste the script in the textbox below into a new text file, and then save it with the extension “.ahk”, which is the AutoHotkey script extension (so you can just call it “AutoHotkeyScript.ahk” for example). You will need to modify the code directory paths and solution file names in the script to match yours to build your solutions, but I’ve commented the script fairly thoroughly so it’s easy to see what it’s doing.</p>

<p>In my office we have both a Client solution and a Server solution, so I have the script setup to build the client solution with WindowsKey+C and the server solution with WindowsKey+S. We also work in multiple branches, so I have global variables near the top of the script that I can set to true to quickly switch between Development, QA, and Release branches. I also have WindowsKey+U configured to open the code directory and WindowsKey+Q to open the database directory. Obviously you can change the keyboard mappings to your liking; these are just the ones that I prefer. As a side note here, just be aware that these will override the default windows key shortcuts; so in my case WindowsKey+U no longer opens up the Windows Ease of Access Center window.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">;</span> <span class="n">IMPORTANT</span> <span class="n">INFO</span> <span class="n">ABOUT</span> <span class="n">GETTING</span> <span class="n">STARTED</span><span class="p">:</span> <span class="n">Lines</span> <span class="n">that</span> <span class="n">start</span> <span class="n">with</span> <span class="n">a</span>
<span class="p">;</span> <span class="n">semicolon</span><span class="p">,</span> <span class="n">such</span> <span class="k">as</span> <span class="k">this</span> <span class="n">one</span><span class="p">,</span> <span class="n">are</span> <span class="n">comments</span><span class="p">.</span> <span class="n">They</span> <span class="n">are</span> <span class="n">not</span> <span class="n">executed</span><span class="p">.</span>

<span class="p">;</span> <span class="n">This</span> <span class="n">script</span> <span class="n">has</span> <span class="n">a</span> <span class="n">special</span> <span class="n">filename</span> <span class="n">and</span> <span class="n">path</span> <span class="n">because</span> <span class="n">it</span> <span class="k">is</span> <span class="n">automatically</span>
<span class="p">;</span> <span class="n">launched</span> <span class="n">when</span> <span class="n">you</span> <span class="n">run</span> <span class="n">the</span> <span class="n">program</span> <span class="n">directly</span><span class="p">.</span> <span class="n">Also</span><span class="p">,</span> <span class="n">any</span> <span class="n">text</span> <span class="n">file</span> <span class="n">whose</span>
<span class="p">;</span> <span class="n">name</span> <span class="n">ends</span> <span class="k">in</span> <span class="p">.</span><span class="n">ahk</span> <span class="k">is</span> <span class="n">associated</span> <span class="n">with</span> <span class="n">the</span> <span class="n">program</span><span class="p">,</span> <span class="n">which</span> <span class="n">means</span> <span class="n">that</span> <span class="n">it</span>
<span class="p">;</span> <span class="n">can</span> <span class="n">be</span> <span class="n">launched</span> <span class="n">simply</span> <span class="k">by</span> <span class="kt">double</span><span class="p">-</span><span class="n">clicking</span> <span class="n">it</span><span class="p">.</span> <span class="n">You</span> <span class="n">can</span> <span class="n">have</span> <span class="k">as</span> <span class="n">many</span> <span class="p">.</span><span class="n">ahk</span>
<span class="p">;</span> <span class="n">files</span> <span class="k">as</span> <span class="n">you</span> <span class="n">want</span><span class="p">,</span> <span class="n">located</span> <span class="k">in</span> <span class="n">any</span> <span class="n">folder</span><span class="p">.</span> <span class="n">You</span> <span class="n">can</span> <span class="n">also</span> <span class="n">run</span> <span class="n">more</span> <span class="n">than</span>
<span class="p">;</span> <span class="n">one</span> <span class="n">ahk</span> <span class="n">file</span> <span class="n">simultaneously</span> <span class="n">and</span> <span class="n">each</span> <span class="n">will</span> <span class="k">get</span> <span class="n">its</span> <span class="n">own</span> <span class="n">tray</span> <span class="n">icon</span><span class="p">.</span>

<span class="p">;</span> <span class="n">Make</span> <span class="n">it</span> <span class="n">so</span> <span class="n">only</span> <span class="n">one</span> <span class="n">instance</span> <span class="n">of</span> <span class="k">this</span> <span class="n">script</span> <span class="n">can</span> <span class="n">run</span> <span class="n">at</span> <span class="n">a</span> <span class="nf">time</span> <span class="p">(</span><span class="n">and</span> <span class="n">reload</span> <span class="n">the</span> <span class="n">script</span> <span class="k">if</span> <span class="n">another</span> <span class="n">instance</span> <span class="n">of</span> <span class="n">it</span> <span class="n">tries</span> <span class="n">to</span> <span class="n">run</span><span class="p">)</span>
<span class="err">#</span><span class="n">SingleInstance</span> <span class="n">force</span>

<span class="p">;==========================================================</span>
<span class="p">;</span> <span class="n">Global</span> <span class="n">Variables</span> <span class="p">-</span> <span class="n">Path</span> <span class="n">settings</span><span class="p">,</span> <span class="n">customization</span><span class="p">,</span> <span class="n">etc</span><span class="p">.</span>
<span class="p">;==========================================================</span>

<span class="p">;</span> <span class="n">Set</span> <span class="n">one</span> <span class="n">of</span> <span class="n">these</span> <span class="n">to</span> <span class="s">"true"</span> <span class="n">to</span> <span class="n">build</span> <span class="k">from</span> <span class="n">the</span> <span class="n">Staging</span> <span class="n">or</span> <span class="n">Release</span> <span class="n">branches</span><span class="p">,</span> <span class="n">otherwise</span> <span class="n">we</span><span class="err">'</span><span class="n">ll</span> <span class="n">use</span> <span class="n">the</span> <span class="n">development</span> <span class="n">branch</span><span class="p">.</span>
<span class="n">_UsingTFSStagingBranch</span> <span class="p">:=</span> <span class="k">false</span>
<span class="n">_UsingTFSReleaseCandidate</span> <span class="p">:=</span> <span class="k">false</span>

<span class="p">;</span> <span class="n">Specify</span> <span class="n">the</span> <span class="n">Code</span> <span class="n">Folder</span> <span class="n">containing</span> <span class="n">the</span> <span class="n">Solution</span> <span class="n">files</span> <span class="n">to</span> <span class="n">build</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_UsingTFSReleaseCandidate</span> <span class="p">==</span> <span class="k">true</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">;</span> <span class="n">The</span> <span class="n">directory</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">build</span><span class="err">'</span><span class="n">s</span> <span class="n">Code</span> <span class="n">folder</span>
    <span class="n">_CodeFolder</span> <span class="p">:=</span> <span class="s">"C:\dev\TFS\RQ4TeamProject\Release\RQ4\4.2.0\"
</span><span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">_UsingTFSStagingBranch</span> <span class="p">==</span> <span class="k">true</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">;</span> <span class="n">The</span> <span class="n">directory</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">build</span><span class="err">'</span><span class="n">s</span> <span class="n">Code</span> <span class="n">folder</span>
    <span class="n">_CodeFolder</span> <span class="p">:=</span> <span class="s">"C:\dev\TFS\RQ4TeamProject\Staging\RQ4\"
</span><span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
    <span class="p">;</span> <span class="n">The</span> <span class="n">directory</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">build</span><span class="err">'</span><span class="n">s</span> <span class="n">Code</span> <span class="n">folder</span>
    <span class="n">_CodeFolder</span> <span class="p">:=</span> <span class="s">"C:\dev\TFS\RQ4TeamProject\Dev\RQ4\Core\"
</span><span class="p">}</span>

<span class="p">;</span> <span class="n">Path</span> <span class="n">to</span> <span class="n">the</span> <span class="n">database</span> <span class="n">folder</span>
<span class="n">_DatabaseFolder</span> <span class="p">:=</span> <span class="s">"C:\dev"</span>

<span class="p">;</span> <span class="n">The</span> <span class="n">path</span> <span class="n">to</span> <span class="n">the</span> <span class="n">Visual</span> <span class="n">Studio</span> <span class="n">Command</span> <span class="n">Prompt</span> <span class="n">link</span>
<span class="n">_VSCommandPromptPath</span> <span class="p">:=</span> <span class="s">"C:\Visual Studio Command Prompt (2010).lnk"</span>
<span class="n">_VSCommandPromptWindowName</span> <span class="p">:=</span> <span class="s">"Administrator: Visual Studio Command Prompt (2010)"</span>

<span class="p">;</span> <span class="n">The</span> <span class="n">position</span> <span class="n">I</span> <span class="n">want</span> <span class="n">the</span> <span class="n">MS</span> <span class="n">Build</span> <span class="n">window</span> <span class="n">to</span> <span class="n">move</span> <span class="n">to</span> <span class="n">when</span> <span class="n">opened</span>
<span class="n">_MSBuildWindowPositionX</span> <span class="p">:=</span> <span class="m">400</span>
<span class="n">_MSBuildWindowPositionY</span> <span class="p">:=</span> <span class="m">270</span>

<span class="p">;</span> <span class="n">The</span> <span class="n">MSBuild</span> <span class="n">command</span> <span class="n">to</span> <span class="n">use</span>
<span class="n">_MSBuildCommand</span> <span class="p">:=</span> <span class="s">"msbuild"</span> <span class="p">;</span> <span class="p">/</span><span class="n">verbosity</span><span class="p">:</span><span class="n">minimal</span><span class="s">"
</span>

<span class="p">;==========================================================</span>
<span class="p">;</span> <span class="n">WindowsKey</span><span class="p">+</span><span class="n">C</span> <span class="p">-</span> <span class="n">Build</span> <span class="n">the</span> <span class="n">Client</span><span class="p">.</span><span class="n">sln</span>
<span class="p">;==========================================================</span>
<span class="err">#</span><span class="n">c</span> <span class="n">UP</span><span class="p">::</span>

<span class="p">;</span> <span class="n">Make</span> <span class="n">sure</span> <span class="n">the</span> <span class="n">keys</span> <span class="n">have</span> <span class="n">been</span> <span class="n">released</span> <span class="n">before</span> <span class="n">continuing</span> <span class="n">to</span> <span class="n">avoid</span> <span class="n">accidental</span> <span class="n">commands</span>
<span class="n">KeyWait</span> <span class="n">LWin</span>
<span class="p">;</span><span class="n">KeyWait</span> <span class="n">c</span>

<span class="p">;</span><span class="nf">BuildSolution</span><span class="p">(</span><span class="n">_CodeFolder</span> <span class="p">.</span> <span class="s">"RQ4.Client.sln"</span><span class="p">)</span>
<span class="nf">BuildSolution</span><span class="p">(</span><span class="s">"RQ4.Client.sln"</span><span class="p">)</span>

<span class="k">return</span>

<span class="p">;==========================================================</span>
<span class="p">;</span> <span class="n">WindowsKey</span><span class="p">+</span><span class="n">S</span> <span class="p">-</span> <span class="n">Build</span> <span class="n">the</span> <span class="n">Server</span><span class="p">.</span><span class="n">sln</span>
<span class="p">;==========================================================</span>
<span class="err">#</span><span class="n">s</span> <span class="n">UP</span><span class="p">::</span>

<span class="p">;</span> <span class="n">Make</span> <span class="n">sure</span> <span class="n">the</span> <span class="n">keys</span> <span class="n">have</span> <span class="n">been</span> <span class="n">released</span> <span class="n">before</span> <span class="n">continuing</span> <span class="n">to</span> <span class="n">avoid</span> <span class="n">accidental</span> <span class="n">commands</span>
<span class="n">KeyWait</span> <span class="n">LWin</span>
<span class="p">;</span><span class="n">KeyWait</span> <span class="n">s</span>

<span class="nf">BuildSolution</span><span class="p">(</span><span class="s">"RQ4.Server.sln"</span><span class="p">)</span>

<span class="k">return</span>

<span class="p">;==========================================================</span>
<span class="p">;</span> <span class="n">WindowsKey</span><span class="p">+</span><span class="n">B</span> <span class="p">-</span> <span class="n">Build</span> <span class="n">the</span> <span class="n">Server</span><span class="p">.</span><span class="n">sln</span> <span class="n">then</span> <span class="n">Client</span><span class="p">.</span><span class="n">sln</span>
<span class="p">;==========================================================</span>
<span class="err">#</span><span class="n">b</span> <span class="n">UP</span><span class="p">::</span>

<span class="p">;</span> <span class="n">Make</span> <span class="n">sure</span> <span class="n">the</span> <span class="n">keys</span> <span class="n">have</span> <span class="n">been</span> <span class="n">released</span> <span class="n">before</span> <span class="n">continuing</span> <span class="n">to</span> <span class="n">avoid</span> <span class="n">accidental</span> <span class="n">commands</span>
<span class="n">KeyWait</span> <span class="n">LWin</span>
<span class="p">;</span><span class="n">KeyWait</span> <span class="n">b</span>

<span class="nf">BuildSolution</span><span class="p">(</span><span class="s">"RQ4.Server.sln"</span><span class="p">)</span>
<span class="nf">BuildSolution</span><span class="p">(</span><span class="s">"RQ4.Client.sln"</span><span class="p">)</span>

<span class="k">return</span>

<span class="p">;==========================================================</span>
<span class="p">;</span> <span class="n">WindowsKey</span><span class="p">+</span><span class="n">U</span> <span class="p">-</span> <span class="n">Open</span> <span class="n">the</span> <span class="n">Code</span> <span class="n">folder</span>
<span class="p">;==========================================================</span>
<span class="err">#</span><span class="n">u</span> <span class="n">UP</span><span class="p">::</span><span class="n">Run</span> <span class="p">%</span><span class="n">_CodeFolder</span><span class="p">%</span>

<span class="p">;==========================================================</span>
<span class="p">;</span> <span class="n">WindowsKey</span><span class="p">+</span><span class="n">Q</span> <span class="p">-</span> <span class="n">Open</span> <span class="n">the</span> <span class="n">Database</span> <span class="n">folder</span>
<span class="p">;==========================================================</span>
<span class="err">#</span><span class="n">q</span> <span class="n">UP</span><span class="p">::</span><span class="n">Run</span> <span class="p">%</span><span class="n">_DatabaseFolder</span><span class="p">%</span>


<span class="p">;==========================================================</span>
<span class="p">;</span> <span class="n">Functions</span>
<span class="p">;==========================================================</span>
<span class="nf">BuildSolution</span><span class="p">(</span><span class="n">solutionPath</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">;</span> <span class="n">Let</span> <span class="k">this</span> <span class="n">function</span> <span class="n">know</span> <span class="n">that</span> <span class="n">all</span> <span class="n">variables</span> <span class="n">except</span> <span class="n">the</span> <span class="n">passed</span> <span class="k">in</span> <span class="n">parameters</span> <span class="n">are</span> <span class="k">global</span> <span class="n">variables</span><span class="p">.</span>
    <span class="k">global</span>

    <span class="p">;</span> <span class="n">If</span> <span class="n">the</span> <span class="n">Visual</span> <span class="n">Studio</span> <span class="n">Command</span> <span class="n">Prompt</span> <span class="k">is</span> <span class="n">already</span> <span class="n">open</span>
    <span class="k">if</span> <span class="nf">WinExist</span><span class="p">(</span><span class="n">_VSCommandPromptWindowName</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="p">;</span> <span class="n">Put</span> <span class="n">it</span> <span class="k">in</span> <span class="n">focus</span>
        <span class="n">WinActivate</span>
    <span class="p">}</span>
    <span class="p">;</span> <span class="n">Else</span> <span class="n">the</span> <span class="n">VS</span> <span class="n">Command</span> <span class="n">Prompt</span> <span class="k">is</span> <span class="n">not</span> <span class="n">already</span> <span class="n">open</span>
    <span class="k">else</span>
    <span class="p">{</span>
        <span class="p">;</span> <span class="n">So</span> <span class="n">open</span> <span class="n">the</span> <span class="n">Visual</span> <span class="n">Studio</span> <span class="m">2008</span> <span class="n">Command</span> <span class="n">Prompt</span>
        <span class="n">Run</span> <span class="p">%</span><span class="n">_VSCommandPromptPath</span><span class="p">%</span>

        <span class="p">;</span> <span class="n">Make</span> <span class="n">sure</span> <span class="k">this</span> <span class="n">window</span> <span class="k">is</span> <span class="k">in</span> <span class="n">focus</span> <span class="n">before</span> <span class="n">sending</span> <span class="n">commands</span>
        <span class="n">WinWaitActive</span><span class="p">,</span> <span class="p">%</span><span class="n">_VSCommandPromptWindowName</span><span class="p">%</span>

        <span class="p">;</span> <span class="n">If</span> <span class="n">the</span> <span class="n">window</span> <span class="n">wasn</span><span class="err">'</span><span class="n">t</span> <span class="n">opened</span> <span class="k">for</span> <span class="n">some</span> <span class="n">reason</span>
        <span class="k">if</span> <span class="n">Not</span> <span class="nf">WinExist</span><span class="p">(</span><span class="n">_VSCommandPromptWindowName</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="p">;</span> <span class="n">Display</span> <span class="n">an</span> <span class="n">error</span> <span class="n">message</span> <span class="n">that</span> <span class="n">the</span> <span class="n">VS</span> <span class="n">Command</span> <span class="n">Prompt</span> <span class="n">couldn</span><span class="err">'</span><span class="n">t</span> <span class="n">be</span> <span class="n">opened</span>
            <span class="n">MsgBox</span><span class="p">,</span> <span class="n">There</span> <span class="n">was</span> <span class="n">a</span> <span class="n">problem</span> <span class="n">opening</span> <span class="p">%</span><span class="n">_VSCommandPromptPath</span><span class="p">%</span>

            <span class="p">;</span> <span class="n">Exit</span><span class="p">,</span> <span class="n">returning</span> <span class="n">failure</span>
            <span class="k">return</span> <span class="k">false</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="p">;</span> <span class="n">Make</span> <span class="n">sure</span> <span class="k">this</span> <span class="n">window</span> <span class="k">is</span> <span class="k">in</span> <span class="n">focus</span> <span class="n">before</span> <span class="n">sending</span> <span class="n">commands</span>
    <span class="n">WinWaitActive</span><span class="p">,</span> <span class="p">%</span><span class="n">_VSCommandPromptWindowName</span><span class="p">%</span>

    <span class="p">;</span> <span class="n">Move</span> <span class="n">the</span> <span class="n">window</span> <span class="n">to</span> <span class="n">the</span> <span class="n">position</span> <span class="n">I</span> <span class="n">like</span> <span class="n">it</span> <span class="n">to</span> <span class="n">be</span>
    <span class="n">WinMove</span><span class="p">,</span> <span class="n">_MSBuildWindowPositionX</span><span class="p">,</span> <span class="n">_MSBuildWindowPositionY</span>

    <span class="p">;</span> <span class="n">Set</span> <span class="n">it</span> <span class="n">to</span> <span class="n">the</span> <span class="n">correct</span> <span class="n">directory</span>
    <span class="n">SendInput</span> <span class="n">cd</span> <span class="p">%</span><span class="n">_CodeFolder</span><span class="p">%</span> <span class="p">{</span><span class="n">Enter</span><span class="p">}</span>

    <span class="p">;</span><span class="n">MsgBox</span> <span class="p">%</span><span class="n">solutionPath</span><span class="p">%</span>  <span class="p">;</span> <span class="n">Message</span> <span class="n">box</span> <span class="n">to</span> <span class="n">display</span> <span class="n">the</span> <span class="n">Solution</span> <span class="n">Path</span> <span class="k">for</span> <span class="n">debugging</span> <span class="n">purposes</span>

    <span class="p">;</span> <span class="n">Build</span> <span class="n">the</span> <span class="n">solution</span> <span class="n">file</span>
    <span class="n">SendInput</span> <span class="p">%</span><span class="n">_MSBuildCommand</span><span class="p">%</span> <span class="p">%</span><span class="n">solutionPath</span><span class="p">%</span> <span class="p">{</span><span class="n">Enter</span><span class="p">}</span>

    <span class="p">;</span> <span class="n">Return</span> <span class="n">success</span>
    <span class="k">return</span> <span class="k">true</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="step-4">Step 4</h2>

<p>Have your AutoHotkey script automatically start when you login to Windows, so that you don’t have to manually launch it all the time.</p>

<h3 id="method-1">Method 1</h3>

<p>This method is the easiest, but I discovered it after Method 2 (below). Simply open up the Windows Start Menu, navigate to the Startup folder within All Programs, right-click on it and choose Open All Users. Then simply paste a shortcut to your AutoHotkey script in this folder. That’s it; the script will now launch whenever any user logs into Windows. If you only want the script to run when <strong>you</strong> log into Windows (no other users), then just choose Open instead of Open All Users when right-clicking on the Startup folder.</p>

<p><img src="/assets/Posts/2012/11/index.png" alt="Open startup directory" />
<img src="/assets/Posts/2012/11/index2.png" alt="Paste shortcut into directory" /></p>

<h3 id="method-2">Method 2</h3>

<p>Open the Windows Task Scheduler and create a new Basic Task. Give it a name and description (something like “launch AutoHotkey script at login”), and then specify to have it run “When I log on”. Then specify that you want it to “Start a program”, and then point it towards the AutoHotkey script you created in Step 3. Before you finish the wizard, check off “Open the Properties dialog for this task when I click Finish”. When that Properties dialog opens up, go to the Conditions tab and make sure none of the checkboxes under the Power category are checked off; this will ensure the script still launches if you are on a laptop and not plugged into AC power. If you need your script to “Run as admin”, then on the General tab check “Run with highest privileges”; this may be required for your script to perform certain actions the require admin privileges, so you can check it off just to be safe.</p>

<p><img src="/assets/Posts/2012/11/open-task-scheduler1.png" alt="Open Task Scheduler" /></p>

<p><img src="/assets/Posts/2012/11/create-basic-task-in-task-scheduler.png" alt="Create Basic Task in Task Scheduler" /></p>

<p><img src="/assets/Posts/2012/11/basic-task-conditions.png" alt="Basic Task Conditions" /></p>

<p><img src="/assets/Posts/2012/11/run-scheduled-task-as-admin_2.png" alt="Run Scheduled Task as Admin" /></p>

<p>And that’s it. Now you can build your solution file in MSBuild with a quick keystroke from anywhere on your computer. I have chosen to use the Windows Key for my shortcut keys, but you don’t have to; you can use whatever keyboard shortcut you want. And feel free to modify the script I provided to do whatever else you want; AutoHotkey is very powerful and can be used for so many things, so be sure to <a href="http://www.autohotkey.com">checkout their website</a> for more examples of scripts and what it can do. For example lots of people use it to automatically spell-correct as they type, or to automatically expand abbreviations (so I could type DS and hit tab, and have it expand to Daniel Schroeder, or type MyAddress and have it put my address in).</p>

<p>Happy Coding!</p>]]></content><author><name>Daniel Schroeder</name></author><category term="AutoHotkey" /><category term="Build" /><category term="Shortcuts" /><category term="AHK" /><category term="AutoHotkey" /><category term="Build" /><category term="keyboard shortcuts" /><category term="MSBuild" /><summary type="html"><![CDATA[One of the greatest benefits of building your solution flies in MSBuild (vs in Visual Studio directly) is that it doesn’t lock up the Visual Studio UI, which can be a huge pain if you have a large solution that takes several minutes (or longer) to build. Building your solution in MSBuild leaves you free to inspect code in Visual Studio while your solution is building. The only problem is that to do this you have to open a command prompt and type the command + path every time to build.]]></summary></entry><entry><title type="html">Automatically deploying TFS Checkin Policies to the entire team</title><link href="https://blog.danskingdom.com/automatically-deploying-tfs-checkin-policies-to-the-entire-team/" rel="alternate" type="text/html" title="Automatically deploying TFS Checkin Policies to the entire team" /><published>2011-05-25T05:19:00+00:00</published><updated>2011-05-25T05:19:00+00:00</updated><id>https://blog.danskingdom.com/automatically-deploying-tfs-checkin-policies-to-the-entire-team</id><content type="html" xml:base="https://blog.danskingdom.com/automatically-deploying-tfs-checkin-policies-to-the-entire-team/"><![CDATA[<p>I stumbled across <a href="http://www.codewrecks.com/blog/index.php/2010/12/04/distributing-visual-studio-addin-for-the-team/">this great post</a> showing how you can use the TFS Power Tools to automatically deploy your custom TFS check-in policies to everyone on your team in Visual Studio 2010. It’s so awesome I had to share.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="TFS" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="Checkin Policies" /><category term="TFS" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><summary type="html"><![CDATA[I stumbled across this great post showing how you can use the TFS Power Tools to automatically deploy your custom TFS check-in policies to everyone on your team in Visual Studio 2010. It’s so awesome I had to share.]]></summary></entry><entry><title type="html">Awesome VS 2010 extensions I can’t live without</title><link href="https://blog.danskingdom.com/awesome-vs-2010-extensions-i-cant-live-without/" rel="alternate" type="text/html" title="Awesome VS 2010 extensions I can’t live without" /><published>2011-05-04T05:15:00+00:00</published><updated>2011-05-04T05:15:00+00:00</updated><id>https://blog.danskingdom.com/awesome-vs-2010-extensions-i-cant-live-without</id><content type="html" xml:base="https://blog.danskingdom.com/awesome-vs-2010-extensions-i-cant-live-without/"><![CDATA[<p>With the ability to extend Visual Studio 2010, lots of great extensions have popped up. These not only increase productivity, but make the whole coding experience much nicer. To find and install extensions, in Visual Studio just go to Tools -&gt; Extension Manager. Here’s a list of my favorites:</p>

<ul>
  <li>
    <p><a href="http://visualstudiogallery.msdn.microsoft.com/e5f41ad9-4edc-4912-bca3-91147db95b99/">PowerCommands for Visual Studio 2010</a> - tons of features.</p>
  </li>
  <li>
    <p><a href="http://visualstudiogallery.msdn.microsoft.com/d0d33361-18e2-46c0-8ff2-4adea1e34fef/">Productivity Power Tools</a> - tons of features.</p>
  </li>
  <li>
    <p>Regex Editor - Quickly test your regular expressions to make sure they match all of your cases, and save them for future use. Also provides some common regex’s by default. Automatically pops up as soon as you type “Regex r = new “.</p>
  </li>
  <li>
    <p>Search Work Items for TFS 2010 - If you’re using TFS this one is a must have; it allows you to easily do a text search on all work items.</p>
  </li>
  <li>
    <p><a href="http://snippetdesigner.codeplex.com/">Snippet Designer</a> - If you create snippets, this provides a great GUI; much better than writing out all of the xml.</p>
  </li>
  <li>
    <p><a href="http://visualstudiogallery.msdn.microsoft.com/7c8341f1-ebac-40c8-92c2-476db8d523ce">Spell Checker</a> - Simple spell checker. Great for fixing spelling mistakes in comments. Automatically knows to ignore variable names, etc.</p>
  </li>
  <li>
    <p><a href="http://vscommands.com/features/">VSCommands 2010</a> - tons of features.</p>
  </li>
</ul>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="Extension" /><category term="Productivity" /><category term="TFS" /><category term="Visual Studio" /><category term="Visual Studio Extensions" /><summary type="html"><![CDATA[With the ability to extend Visual Studio 2010, lots of great extensions have popped up. These not only increase productivity, but make the whole coding experience much nicer. To find and install extensions, in Visual Studio just go to Tools -&gt; Extension Manager. Here’s a list of my favorites:]]></summary></entry><entry><title type="html">Friendly Visual Studio solution names for working with multiple branches</title><link href="https://blog.danskingdom.com/friendly-visual-studio-solution-names-for-working-with-multiple-branches/" rel="alternate" type="text/html" title="Friendly Visual Studio solution names for working with multiple branches" /><published>2011-04-30T05:16:00+00:00</published><updated>2011-04-30T05:16:00+00:00</updated><id>https://blog.danskingdom.com/friendly-visual-studio-solution-names-for-working-with-multiple-branches</id><content type="html" xml:base="https://blog.danskingdom.com/friendly-visual-studio-solution-names-for-working-with-multiple-branches/"><![CDATA[<p>If you have the latest version of the Visual Studio 2010 extension <a href="http://vscommands.com">VSCommands</a> you can give your solutions friendly names that display in the window’s title bar.  This is nice when you are working in different branches, so that you can differentiate which solution you are actually looking at.  I wrote the following regex to put the Branch Name after the Solution name, so for example if you have the client solution open in both Dev and Release, one will be called “Client.sln – Dev” and the other “Client.sln – Release”.</p>

<p>To use this, in Visual Studio go to Tools -&gt; Options -&gt; VSCommands 2010-&gt; IDE Enhancements and then paste in the following:</p>

<p>Friendly Name: <code class="language-plaintext highlighter-rouge">{solutionName} - {branchName}</code></p>

<p>Friendly Name – Solution Path Regex: <code class="language-plaintext highlighter-rouge">.*\(?&lt;branchName&gt;.*)\(?&lt;solutionName&gt;.*(?:.sln))</code></p>

<p><img src="/assets/Posts/2012/11/windowtitle1.png" alt="Window title 1" /></p>

<p><img src="/assets/Posts/2012/11/windowtitle2.png" alt="Window title 2" /></p>

<p>Happy coding!</p>

<h3 id="-update-">– Update –</h3>

<p>Here is the new regex that I prefer to use instead now which shows the directories that the solution is sitting in:</p>

<p>Friendly Name: <code class="language-plaintext highlighter-rouge">{solutionName} - {dir1}{dir2}{dir3}</code></p>

<p>Regex: <code class="language-plaintext highlighter-rouge">.*\(?&lt;dir1&gt;.*)\(?&lt;dir2&gt;.*)\(?&lt;dir3&gt;.*)\(?&lt;solutionName&gt;.*(.sln)Z)</code></p>

<h3 id="-update-2-for-vs-2012-">– Update 2 for VS 2012 –</h3>

<p>These are the settings that I like to use for VS Commands 11 for VS 2012:</p>

<p>Branch Name Regex: <code class="language-plaintext highlighter-rouge">.*\(?&lt;dir1&gt;.*)\(?&lt;dir2&gt;.*)\(?&lt;branchDirectoryName&gt;.*)\(?&lt;solutionFileName&gt;.*(.sln)Z)</code></p>

<p>Branch Name Pattern: <code class="language-plaintext highlighter-rouge">{branchDirectoryName} Branch</code></p>

<p>Git Branch Name Pattern: <code class="language-plaintext highlighter-rouge">{git:head} Branch</code></p>

<p>Main Window Title Pattern: <code class="language-plaintext highlighter-rouge">{solutionFileName} - {dir1}{dir2}{branchDirectoryName} ({sln:activeConfig}|{sln:activePlatform})</code></p>

<p>Solution Explorer Window Title Pattern: <code class="language-plaintext highlighter-rouge">" - {solutionFileName} • {vsc:branchName}"</code> (without the quotes)</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Visual Studio" /><category term="Visual Studio Extensions" /><category term="Friendly Name" /><category term="solution" /><category term="Visual Studio" /><category term="VSCommands" /><summary type="html"><![CDATA[If you have the latest version of the Visual Studio 2010 extension VSCommands you can give your solutions friendly names that display in the window’s title bar.  This is nice when you are working in different branches, so that you can differentiate which solution you are actually looking at.  I wrote the following regex to put the Branch Name after the Solution name, so for example if you have the client solution open in both Dev and Release, one will be called “Client.sln – Dev” and the other “Client.sln – Release”.]]></summary></entry><entry><title type="html">TFS GoToWorkItem VS command and keyboard shortcut</title><link href="https://blog.danskingdom.com/tfs-gotoworkitem-vs-command-and-keyboard-shortcut/" rel="alternate" type="text/html" title="TFS GoToWorkItem VS command and keyboard shortcut" /><published>2011-04-30T05:06:00+00:00</published><updated>2011-04-30T05:06:00+00:00</updated><id>https://blog.danskingdom.com/tfs-gotoworkitem-vs-command-and-keyboard-shortcut</id><content type="html" xml:base="https://blog.danskingdom.com/tfs-gotoworkitem-vs-command-and-keyboard-shortcut/"><![CDATA[<p>The button to jump directly to a work item by specifying its ID looks to be on the Work Item Tracking toolbar by default in VS / TFS 2010. This button is not on the toolbar by default in VS / TFS 2008 though. To add it yourself just go to Tools =&gt; Customize, then choose the category Team and the command Go To Work Item…, and you can drag the command into one of your existing toolbars.</p>

<p>If you want to setup a keyboard shortcut for the command, just go to Tools =&gt; Options =&gt; Environment =&gt; Keyboard, and the command is called Team.GotoWorkItem. I map it to <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>/</kbd> since <kbd>Ctrl</kbd> + <kbd>/</kbd> is the C# keyboard shortcut to search in a file.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Shortcuts" /><category term="TFS" /><category term="keyboard shortcuts" /><category term="TFS" /><category term="Visual Studio" /><summary type="html"><![CDATA[The button to jump directly to a work item by specifying its ID looks to be on the Work Item Tracking toolbar by default in VS / TFS 2010. This button is not on the toolbar by default in VS / TFS 2008 though. To add it yourself just go to Tools =&gt; Customize, then choose the category Team and the command Go To Work Item…, and you can drag the command into one of your existing toolbars.]]></summary></entry><entry><title type="html">Solution won’t build on TFS Build Server</title><link href="https://blog.danskingdom.com/solution-wont-build-on-tfs-build-server/" rel="alternate" type="text/html" title="Solution won’t build on TFS Build Server" /><published>2011-04-18T04:58:00+00:00</published><updated>2011-04-18T04:58:00+00:00</updated><id>https://blog.danskingdom.com/solution-wont-build-on-tfs-build-server</id><content type="html" xml:base="https://blog.danskingdom.com/solution-wont-build-on-tfs-build-server/"><![CDATA[<p>If you are able to build (compile) the solution on your local machine, but it won’t build on the TFS build server and you are getting an error message similar to:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1 error(s), 1 warning(s)
$/TeamProject/Dev/RQ4/FBs/4.1.4_eSec/RQ4.Client.sln - 1 error(s), 1 warning(s), View Log File
IntegrationfrmGetIntegrationAuthorization.xaml.cs (1138): The type or namespace name 'BillingFields' could not be found (are you missing a using directive or an assembly reference?)
C:WindowsMicrosoft.NETFramework64v4.0.30319Microsoft.Common.targets (1360): Could not resolve this reference. Could not locate the assembly "IQ.Core.Resources, Version=4.1.2.0, Culture=neutral, processorArchitecture=MSIL". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors.
</code></pre></div></div>

<p>Then the problem is likely one of the following:</p>

<ol>
  <li>The project/dll being reference is set to “Specific Version”, so open the reference’s properties and change it to “Any Version”.</li>
  <li>The project is not set to be built under the specific configuration. Right click on the solution in the Solution Explorer, and choose Configuration Manager. All projects should be set to be build (i.e. have a checkmark), and all of their platforms should be set to the same value. Change the Active Solution Platform to the platform you use and ensure that all projects are still set to always build.</li>
  <li>The path to the referenced project/dll is too long. Windows/.NET has a limitation where the reference path cannot be more than 260 characters, and the directory it’s in cannot be more than 248 characters. So the work around for this is usually to rename your build definition to something shorter, or if you just added a new project/namespace to the project, to shorten its name so the path stays under the limit.</li>
</ol>]]></content><author><name>Daniel Schroeder</name></author><category term="Build" /><category term="TFS" /><category term="Build" /><category term="Build Server" /><category term="compile" /><category term="solution" /><category term="TFS" /><summary type="html"><![CDATA[If you are able to build (compile) the solution on your local machine, but it won’t build on the TFS build server and you are getting an error message similar to:]]></summary></entry><entry><title type="html">Adding ValueChanged events to Dependency Objects in WPF</title><link href="https://blog.danskingdom.com/adding-valuechanged-events-to-dependency-objects-in-wpf/" rel="alternate" type="text/html" title="Adding ValueChanged events to Dependency Objects in WPF" /><published>2011-04-18T04:56:00+00:00</published><updated>2011-04-18T04:56:00+00:00</updated><id>https://blog.danskingdom.com/adding-valuechanged-events-to-dependency-objects-in-wpf</id><content type="html" xml:base="https://blog.danskingdom.com/adding-valuechanged-events-to-dependency-objects-in-wpf/"><![CDATA[<p>You may be wondering which is the best way to hookup a DependencyProperty’s Callback event handler to handle Value Changed events. The two methods to consider are:</p>

<p>Method 1 - Use static event handlers, like so:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">virtual</span> <span class="kt">int</span> <span class="n">SelectedID</span>
<span class="p">{</span>
    <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">SelectedIDProperty</span><span class="p">);</span> <span class="p">}</span>
    <span class="k">set</span> <span class="p">{</span> <span class="nf">SetValue</span><span class="p">(</span><span class="n">SelectedIDProperty</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span> <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">DependencyProperty</span> <span class="n">SelectedIDProperty</span> <span class="p">=</span>
    <span class="n">DependencyProperty</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="s">"SelectedID"</span><span class="p">,</span> <span class="k">typeof</span><span class="p">(</span><span class="kt">int</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">SelectorBase</span><span class="p">),</span>
        <span class="k">new</span> <span class="nf">PropertyMetadata</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="k">new</span> <span class="nf">PropertyChangedCallback</span><span class="p">(</span><span class="n">OnSelectedIDChanged</span><span class="p">)));</span>

<span class="k">private</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">OnSelectedIDChanged</span><span class="p">(</span><span class="n">DependencyObject</span> <span class="n">d</span><span class="p">,</span> <span class="n">DependencyPropertyChangedEventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Perform event handler logic</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Method 2 - Hookup event handler at initialize, and remove it during Dispose(), like so:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Constructor</span>
<span class="k">public</span> <span class="nf">SelectorBase</span><span class="p">()</span>
<span class="p">{</span>
    <span class="nf">HookupEventHandlers</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">void</span> <span class="nf">HookupEventHandlers</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">TypeDescriptor</span><span class="p">.</span><span class="nf">GetProperties</span><span class="p">(</span><span class="k">this</span><span class="p">)[</span><span class="s">"SelectedID"</span><span class="p">].</span><span class="nf">AddValueChanged</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">SelectedID_ValueChanged</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">void</span> <span class="nf">RemoveEventHandlers</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">TypeDescriptor</span><span class="p">.</span><span class="nf">GetProperties</span><span class="p">(</span><span class="k">this</span><span class="p">)[</span><span class="s">"SelectedID"</span><span class="p">].</span><span class="nf">RemoveValueChanged</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">SelectedID_ValueChanged</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Dispose</span><span class="p">(</span><span class="kt">bool</span> <span class="n">isDisposing</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">base</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">(</span><span class="n">isDisposing</span><span class="p">);</span>
    <span class="c1">// If managed resources should be released</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">isDisposing</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="nf">RemoveEventHandlers</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">virtual</span> <span class="kt">int</span> <span class="n">SelectedID</span>
<span class="p">{</span>
    <span class="k">get</span> <span class="p">{</span> <span class="k">return</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="nf">GetValue</span><span class="p">(</span><span class="n">SelectedIDProperty</span><span class="p">);</span> <span class="p">}</span>
    <span class="k">set</span> <span class="p">{</span> <span class="nf">SetValue</span><span class="p">(</span><span class="n">SelectedIDProperty</span><span class="p">,</span> <span class="k">value</span><span class="p">);</span> <span class="p">}</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">DependencyProperty</span> <span class="n">SelectedIDProperty</span> <span class="p">=</span>
    <span class="n">DependencyProperty</span><span class="p">.</span><span class="nf">Register</span><span class="p">(</span><span class="s">"SelectedID"</span><span class="p">,</span> <span class="k">typeof</span><span class="p">(</span><span class="kt">int</span><span class="p">),</span> <span class="k">typeof</span><span class="p">(</span><span class="n">SelectorBase</span><span class="p">),</span> <span class="k">new</span> <span class="nf">PropertyMetadata</span><span class="p">(</span><span class="m">0</span><span class="p">));</span>

<span class="k">private</span> <span class="k">void</span> <span class="nf">SelectedID_ValueChanged</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// Perform event handler logic</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So the advantage to using method 1 is that we have access to the property’s old and new values, we <a href="http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/6f18c879-6ea4-4473-b316-30c4fd5f43b5">don’t have to worry about memory leaks</a> (since the event handler is static), and if we create 100 instances of the control we still only have one static event handler in memory, instead of 100 local ones. The disadvantage to method 1 is that these event handlers are going to exist in memory for the entire lifetime of the app, even if the view/control they are on is never referenced.</p>

<p>The advantage to using method 2 is that the event handlers only exist in memory if the view/control they are on is actually open. The disadvantage is that we don’t have access to the property’s old value, and the developer has to remember to properly unhook the event in order to avoid a memory leak.</p>

<p>So method 1 is best suited to items that are used in many places (such as custom controls that may be plastered all of the place), while method 2 is best suited for views where there is likely to never be more than a few instances open at any given time, or in places that may not be accessed at all (e.g. a settings menu that is rarely accessed).</p>]]></content><author><name>Daniel Schroeder</name></author><category term="WPF" /><category term="CSharp" /><category term="Dependency Object" /><category term="memory leak" /><category term="Value Changed" /><category term="ValueChanged" /><category term="WPF" /><summary type="html"><![CDATA[You may be wondering which is the best way to hookup a DependencyProperty’s Callback event handler to handle Value Changed events. The two methods to consider are:]]></summary></entry><entry><title type="html">My WPF Binding won’t work. WTF!</title><link href="https://blog.danskingdom.com/my-wpf-binding-wont-work-wtf/" rel="alternate" type="text/html" title="My WPF Binding won’t work. WTF!" /><published>2011-04-18T04:48:00+00:00</published><updated>2011-04-18T04:48:00+00:00</updated><id>https://blog.danskingdom.com/my-wpf-binding-wont-work-wtf</id><content type="html" xml:base="https://blog.danskingdom.com/my-wpf-binding-wont-work-wtf/"><![CDATA[<p>At one point or another I’m sure we’ve all been confused as to why our binding won’t work. Here’s a couple things to keep in mind:</p>

<ul>
  <li>
    <p>have you set the <code class="language-plaintext highlighter-rouge">DataContext</code> to be the class containing the property you are binding to? This can be done in XAML or in the code-behind. For example, put “this.DataContext = this” in the code-behind file’s constructor.</p>
  </li>
  <li>
    <p>is the property you’re binding to public? Also, it must be a property (with <code class="language-plaintext highlighter-rouge">get; set;</code> accessors), not a field (i.e. class variable).</p>
  </li>
  <li>
    <p>if you are using two way binding, do you have both a Get and Set accessor on the property?</p>
  </li>
  <li>
    <p>if you are trying to set bindings on a user control that you are placing on your form, you may have to set the <code class="language-plaintext highlighter-rouge">DataContext</code> in that control, or reference the property dynamically using something like:</p>
  </li>
</ul>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="n">controls</span><span class="p">:</span><span class="n">CashCalculator</span>
  <span class="n">CanModifyAmount</span><span class="p">=</span><span class="s">"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:WpfViewBase}},
</span>  <span class="n">Path</span><span class="p">=</span><span class="n">CashOutViewModel</span><span class="p">.</span><span class="n">CanModifyAmount</span><span class="p">,</span> <span class="n">Mode</span><span class="p">=</span><span class="n">OneWay</span><span class="p">}</span><span class="s">"
</span>  <span class="n">CashDetail</span><span class="p">=</span><span class="s">"{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:WpfViewBase}},
</span>  <span class="n">Path</span><span class="p">=</span><span class="n">CashOutViewModel</span><span class="p">.</span><span class="n">CashOutModel</span><span class="p">.</span><span class="n">CashOut</span><span class="p">.</span><span class="n">CashDetail</span><span class="p">,</span> <span class="n">Mode</span><span class="p">=</span><span class="n">OneWay</span><span class="p">}</span><span class="s">" /&gt;
</span></code></pre></div></div>

<p>If you still don’t know why your binding isn’t working, be sure to check the Output window in Visual Studio while debugging, as it will display any binding errors that occur and (hopefully) give you more information about the problem. You can also change the level of verbosity that is displayed for Binding errors, so if you aren’t getting enough information, in Visual Studio go into Tools -&gt; Options -&gt; Debugging -&gt; Output Window and make sure Data Binding is at least set to Warning.</p>

<p>If changes made to the UI propagate to the code-behind, but changes made in the code-behind don’t propagate to the UI, you will either need to use the <a href="http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx#Y228">INotifyPropertyChanged</a> pattern, or implement your code-behind properties as <a href="http://msdn.microsoft.com/en-us/library/ms752914.aspx">DependencyProperties</a>, in order to let the UI know that the value has been updated and show the change.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="Binding" /><category term="WPF" /><category term="Binding" /><category term="CSharp" /><category term="WPF" /><category term="XAML" /><summary type="html"><![CDATA[At one point or another I’m sure we’ve all been confused as to why our binding won’t work. Here’s a couple things to keep in mind:]]></summary></entry><entry><title type="html">Setting focus to an element in WPF</title><link href="https://blog.danskingdom.com/setting-focus-to-an-element-in-wpf/" rel="alternate" type="text/html" title="Setting focus to an element in WPF" /><published>2011-04-18T04:44:00+00:00</published><updated>2011-04-18T04:44:00+00:00</updated><id>https://blog.danskingdom.com/setting-focus-to-an-element-in-wpf</id><content type="html" xml:base="https://blog.danskingdom.com/setting-focus-to-an-element-in-wpf/"><![CDATA[<p>So if you are trying to set focus to a WPF element, but are unable to. Here is a quick checklist to go through:</p>

<ul>
  <li>is the control you are trying to set focus to <code class="language-plaintext highlighter-rouge">Enabled</code>, <code class="language-plaintext highlighter-rouge">Visible</code>, <code class="language-plaintext highlighter-rouge">Loaded</code>, and <code class="language-plaintext highlighter-rouge">Focusable</code>. If any of these properties are false, you cannot set focus to the element.</li>
</ul>

<p>If you are using Binding to set these properties, make sure the binding is firing before you are trying to set the focus.</p>

<ul>
  <li>does the control that already has focus allow focus to be taken away? If the control that currently has focus overrides the <code class="language-plaintext highlighter-rouge">PreviewLostFocus</code> event, it can set <code class="language-plaintext highlighter-rouge">e.Handled</code> to true to prevent other controls from stealing focus from it.</li>
</ul>

<p>If all of these conditions seem to be met, but you still cannot seem to set the focus to a control, is another operation moving focus from your control to another control after you set focus to your control? From a dispatcher perhaps?</p>

<p>Using the tool <a href="http://snoopwpf.codeplex.com/">Snoop</a> is great for viewing what control in your WPF application has focus, and is very useful in debugging for seeing what controls are receiving focus. Once you know what control is getting focus instead of your control, you can put a breakpoint in the control’s GotFocus event to debug and see when focus is being moved to the control, and what function is performing the operation to move the focus.</p>]]></content><author><name>Daniel Schroeder</name></author><category term="WPF" /><category term="CSharp" /><category term="focus" /><category term="setting focus" /><category term="snoop" /><category term="WPF" /><summary type="html"><![CDATA[So if you are trying to set focus to a WPF element, but are unable to. Here is a quick checklist to go through:]]></summary></entry><entry><title type="html">Some Visual Studio 2010 Shortcuts and C# 4.0 Cool Stuff</title><link href="https://blog.danskingdom.com/some-visual-studio-2010-shortcuts-and-c-4-0-cool-stuff/" rel="alternate" type="text/html" title="Some Visual Studio 2010 Shortcuts and C# 4.0 Cool Stuff" /><published>2011-04-18T04:40:00+00:00</published><updated>2011-04-18T04:40:00+00:00</updated><id>https://blog.danskingdom.com/some-visual-studio-2010-shortcuts-and-c-4-0-cool-stuff</id><content type="html" xml:base="https://blog.danskingdom.com/some-visual-studio-2010-shortcuts-and-c-4-0-cool-stuff/"><![CDATA[<p>A list of some shortcuts and new features to VS 2010 and C# 4.0:</p>

<ul>
  <li>Default values for parameters</li>
  <li>Can access parameters by name (i.e. <code class="language-plaintext highlighter-rouge">SomeFunction(name: "Dan", age: 26);</code>)</li>
  <li>Can now put Labels on breakpoints and filter the breakpoints, as well as import and export breakpoints.</li>
  <li>Window =&gt; New Window to open up same file in two separate tabs, or can drag the splitter at the top-right corner of the edit indow.</li>
  <li>Edit =&gt; Outlining =&gt; Hide Selection to collapse any region of code</li>
  <li><kbd>Alt</kbd> + Mouse Left Drag for box selection instead of line selection, then just start typing; you can also use Alt+Shift+Arrow eys to do box selection with the keyboard.</li>
  <li><kbd>Alt</kbd> + Arrow Keys to move current line up/down. Can also select multiple lines and use <kbd>Alt</kbd> + <kbd>Up</kbd> / <kbd>Down</kbd> to move the whole selection p/down.</li>
  <li>In NavigateTo search window (<kbd>Ctrl</kbd> + <kbd>Comma</kbd>) use capitals to search for camel casing (i.e. CE to find displayCustomerEmails) nd a space to do an “and” search (i.e. “email customer” would find displayCustomerEmails).</li>
  <li><kbd>Ctrl</kbd> + <kbd>I</kbd> to do an incremental search of a document, then <kbd>F3</kbd> and <kbd>Shift</kbd> + <kbd>F3</kbd> to move to next/previous matches.</li>
  <li>Use snippets to automatically create code and save time.</li>
  <li><kbd>Ctrl</kbd> + <kbd>.</kbd> to access VS tickler window instead of having to hover over the variable with the mouse.</li>
  <li><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Spacebar</kbd> to change VS to suggest variable names instead of auto completing them.</li>
  <li>Can right click in document to remove and sort using statements.</li>
  <li><code class="language-plaintext highlighter-rouge">Enum.TryParse()</code> has been added to match a string or number to an enumerated type.</li>
  <li><code class="language-plaintext highlighter-rouge">Contract.Requires()</code> and <code class="language-plaintext highlighter-rouge">.Ensures()</code> to ensure that function conditions are met (at compile time).</li>
  <li><code class="language-plaintext highlighter-rouge">String.IsNullOrWhitespace(string);</code></li>
  <li><code class="language-plaintext highlighter-rouge">Lazy&lt;T&gt;</code> for thread-safe lazy loading of variables.</li>
  <li>VS =&gt; Options =&gt; Debugging =&gt; Output Window =&gt; Data Binding to give more info about errors.</li>
  <li>Using <code class="language-plaintext highlighter-rouge">System.Threading.Tasks</code> for parallel processing. <code class="language-plaintext highlighter-rouge">Parallel.For()</code> and <code class="language-plaintext highlighter-rouge">.ForEach()</code>.</li>
  <li>PLINQ =&gt; <code class="language-plaintext highlighter-rouge">myCollection.InParallel().Where(x =&gt; ...);</code></li>
  <li>New <code class="language-plaintext highlighter-rouge">Dynamic</code> keyword type =&gt; just like Object except not checked at compile time.</li>
  <li><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> to cycle through clipboard ring</li>
  <li><kbd>Alt</kbd> + <kbd>Ctrl</kbd> + <kbd>Down</kbd> to access tab menu</li>
  <li><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Up</kbd> / <kbd>Down</kbd> to move between instances of the highlighted variable</li>
  <li><kbd>Ctrl</kbd> + <kbd>]</kbd> to move back and forth between a functions opening and closing braces (i.e. “{“ and “}”). This appears to also work in XAML!</li>
  <li><kbd>Alt</kbd> + <kbd>Arrow Keys</kbd> to move current line up/down. Can also select multiple lines and use <kbd>Alt</kbd> + <kbd>Up</kbd> / <kbd>Down</kbd> to move the whole selection up/down.</li>
  <li>Rather than selecting a whole line first, just use <kbd>Ctrl</kbd> + <kbd>C</kbd> or <kbd>Ctrl</kbd> + <kbd>X</kbd> to Copy/Cut the entire line. You can also use <kbd>Shift</kbd> + <kbd>Delete</kbd> to delete an entire line without selecting it.</li>
</ul>]]></content><author><name>Daniel Schroeder</name></author><category term="CSharp" /><category term="Shortcuts" /><category term="Visual Studio" /><category term="CSharp" /><category term="features" /><category term="keyboard shortcuts" /><category term="shortcuts" /><category term="Visual Studio" /><summary type="html"><![CDATA[A list of some shortcuts and new features to VS 2010 and C# 4.0:]]></summary></entry></feed>