<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sean on IT</title><link>https://seanonit.org/</link><description>Recent content on Sean on IT</description><generator>Hugo</generator><language>en</language><copyright>Copyright © 2014-2026 Sean D. Wheeler. All Rights Reserved.</copyright><lastBuildDate>Sat, 04 Apr 2026 00:00:00 -0500</lastBuildDate><atom:link href="https://seanonit.org/index.xml" rel="self" type="application/rss+xml"/><item><title>The Legend of PlatyPS and Content Wrangler</title><link>https://seanonit.org/blog/2025/08/the-legend-of-platyps-and-content-wrangler/</link><pubDate>Mon, 04 Aug 2025 00:00:00 -0500</pubDate><guid>https://seanonit.org/blog/2025/08/the-legend-of-platyps-and-content-wrangler/</guid><description><![CDATA[<p>Recently I have been working on a big project to rewrite the <a href="https://learn.microsoft.com/powershell/utility-modules/platyps/overview" target="_blank" rel="noopener noreferrer">PlatyPS module<i class="fas fa-external-link-square-alt ms-1"></i></a>. This is the
module we use at Microsoft to create and maintain the PowerShell documentation. As part of this
project, I spent a long time fixing existing PowerShell content so that it would work with the new
PlatyPS module. To celebrate the completion of this project, I decided to have some fun with
Microsoft Copilot. I created this image and story about the PlatyPS module and how it helps us
wrangle PowerShell documentation.</p>]]></description><content:encoded><![CDATA[<p>Recently I have been working on a big project to rewrite the <a href="https://learn.microsoft.com/powershell/utility-modules/platyps/overview" target="_blank" rel="noopener noreferrer">PlatyPS module<i class="fas fa-external-link-square-alt ms-1"></i></a>. This is the
module we use at Microsoft to create and maintain the PowerShell documentation. As part of this
project, I spent a long time fixing existing PowerShell content so that it would work with the new
PlatyPS module. To celebrate the completion of this project, I decided to have some fun with
Microsoft Copilot. I created this image and story about the PlatyPS module and how it helps us
wrangle PowerShell documentation.</p>
<hr>
<p><picture><img class="img-fluid " alt="PlatyPS and Content Wrangler" src="/blog/2025/08/the-legend-of-platyps-and-content-wrangler/platyps-content-wrangler.png" loading="lazy" width="1536" height="1024" />
</picture>

</p>
<p>Out on the wild edges of the PowerShell frontier, where modules multiplied and help files were as
scarce as water in the Death Valley, there rode a man named <strong>Sean Wheeler</strong>. With a laptop in one
hand and a coffee-stained cowboy hat in the other. Sean was a rare breed: part coder, part cowboy,
and full-time documentarian.</p>
<p>His companion? A massive, bright-blue, duck-billed beast named <strong>PlatyPS</strong>.</p>
<p>Now, PlatyPS wasn&rsquo;t some ordinary companion. No, sir. He was bred for one purpose: <em>to wrangle
PowerShell documentation</em>. When other developers were getting bucked off by <code>Get-Help</code> and dragged
by <code>Update-Help</code>, Sean and PlatyPS rode in, calm as a Sunday morning, turning chaos into clarity.</p>
<p>With a single command, Sean could turn complex cmdlets into clean, readable markdown:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Get-Command -Module TrailblazerTools | New-MarkdownCommandHelp -OutputFolder .\docs
</span></span></code></pre></div><p>Need to refresh it after a long day on the dev range?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Update-MarkdownHelp -Path .\docs
</span></span></code></pre></div><p>And when it was time to publish those help files for the whole PowerShell posse to use:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Import-MarkdownCommandHelp .\docs | Export-MamlCommandHelp -OutputPath .\en-US
</span></span></code></pre></div><p>Thanks to Sean and PlatyPS, no module went undocumented, and no parameter went unexplained. They
brought law and order to the wide, wild west of scripting, one Markdown file at a time.</p>
<p>So if you ever find yourself knee-deep in a PowerShell module with no documentation in sight, don&rsquo;t
panic. Just install the legend:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Install-PSResource -Name Microsoft.PowerShell.PlatyPS
</span></span></code></pre></div><p>And tip your hat to <strong>Sean Wheeler</strong>, the cowboy who taught the world that good code deserves even
better docs.</p>
]]></content:encoded></item><item><title>Using Git from PowerShell</title><link>https://seanonit.org/blog/2016/12/using-git-from-powershell/</link><pubDate>Mon, 05 Dec 2016 00:00:00 -0600</pubDate><guid>https://seanonit.org/blog/2016/12/using-git-from-powershell/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed. For a more up-to-date discussion of this topic, see
<a href="https://sdwheeler.github.io/seanonit/docs/04-github/" target="_blank" rel="noopener noreferrer">Taming Git and GitHub with PowerShell<i class="fas fa-external-link-square-alt ms-1"></i></a> on my Presentations site.</p>
</blockquote>
<p>This year has been full of changes for me. One of the biggest changes is that my job requires me to
use Git and GitHub for almost all of my work. Before this new job, I had never used Git. By default,
the Git installer installs the bash command shell. Most of the documentation is written assuming
that you are using bash. However, I prefer to work in PowerShell. In this article I will show how I
set up my environment to enable Git functionality in PowerShell. This is not meant to be a tutorial
on using Git but, rather, a example of what works for me and for my workflow.</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed. For a more up-to-date discussion of this topic, see
<a href="https://sdwheeler.github.io/seanonit/docs/04-github/" target="_blank" rel="noopener noreferrer">Taming Git and GitHub with PowerShell<i class="fas fa-external-link-square-alt ms-1"></i></a> on my Presentations site.</p>
</blockquote>
<p>This year has been full of changes for me. One of the biggest changes is that my job requires me to
use Git and GitHub for almost all of my work. Before this new job, I had never used Git. By default,
the Git installer installs the bash command shell. Most of the documentation is written assuming
that you are using bash. However, I prefer to work in PowerShell. In this article I will show how I
set up my environment to enable Git functionality in PowerShell. This is not meant to be a tutorial
on using Git but, rather, a example of what works for me and for my workflow.</p>

<h2 id="download-and-install-the-git-for-windows" data-numberify>Download and install the Git for Windows<a class="anchor ms-1" href="#download-and-install-the-git-for-windows"></a></h2>
<p>First thing is to install Git for Windows. Download and run the <a href="https://git-for-windows.github.io/" target="_blank" rel="noopener noreferrer">Git for Windows<i class="fas fa-external-link-square-alt ms-1"></i></a> installer. As
you step through the installation wizard you are presented with several options. The following is a
list of the options on each page of the installation wizard with the reasoning behind my choice.</p>
<ul>
<li>The <strong>Select Components</strong> page
<ul>
<li>Check <strong>Git LFS (Large File Support)</strong></li>
<li>Check <strong>Associate .git* configuration files with the default text</strong></li>
<li>Check <strong>Use a TrueType font in all console windows</strong> I prefer the TrueType font Consolas as my
monospaced font for command shells and code editors.</li>
</ul>
</li>
<li>The <strong>Choosing the default editor used by Git</strong> page
<ul>
<li>Select <strong>Use Visual Studio Code as Git&rsquo;s default editor</strong> VS Code does everything.</li>
</ul>
</li>
<li>The <strong>Adjusting your PATH environment</strong> page
<ul>
<li>Select <strong>Use Git from the Windows Command Prompt</strong> This adds the Git tools to your PATH so that
it works for Cmd, PowerShell, or bash.</li>
</ul>
</li>
<li>The <strong>Choosing HTTPS transport backend</strong> page
<ul>
<li>Select <strong>Use the native Windows Secure Channel library</strong></li>
</ul>
</li>
<li>The <strong>Configure the line ending conversions</strong> page
<ul>
<li>Select <strong>Checkout Windows-style, commit Unix-style line endings</strong> This is the recommended
setting on Windows and provides the most compatibility for cross-platform projects.</li>
</ul>
</li>
<li>The <strong>Configuring the terminal emulator to use with Git bash</strong> page
<ul>
<li>Select <strong>Use Windows&rsquo; default console window</strong> This is the console that PowerShell uses and
works best with other Windows console-based applications.</li>
</ul>
</li>
<li>The <strong>Configuring extra options</strong> page
<ul>
<li>Check <strong>Enable file system caching</strong> This option is checked by default. Caching improves
performance of certain Git operations.</li>
<li>Check <strong>Enable Git Credential Manager</strong> The Git Credential Manager for Windows (GCM) provides
secure Git credential storage for Windows. GCM provides multi-factor authentication support for
Visual Studio Team Services, Team Foundation Server, and GitHub. Enabling GCM prevents the need
for Git to continuously prompt for your Git credentials for nearly every operation. For more
information see the <a href="https://github.com/Microsoft/Git-Credential-Manager-for-Windows" target="_blank" rel="noopener noreferrer">GCM documentation<i class="fas fa-external-link-square-alt ms-1"></i></a> on GitHub.</li>
<li>Check <strong>Enable symbolic links</strong></li>
</ul>
</li>
</ul>
<p>These are the options I chose. You may have different requirements in your environment.</p>

<h2 id="install-the-posh-git-module" data-numberify>Install the Posh-Git module<a class="anchor ms-1" href="#install-the-posh-git-module"></a></h2>
<p>Now that we have the Git client installed we need to enable Git functionality for PowerShell.
<a href="https://www.powershellgallery.com/packages/posh-git" target="_blank" rel="noopener noreferrer">Posh-Git<i class="fas fa-external-link-square-alt ms-1"></i></a> from the Gallery. For more information about Posh-Git, see <a href="https://github.com/dahlbyk/posh-git" target="_blank" rel="noopener noreferrer">Posh-Git on GitHub<i class="fas fa-external-link-square-alt ms-1"></i></a>.
If you have PsGet installed just run:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Install-Module posh-git
</span></span></code></pre></div><p>Alternatively, you can install Posh-Git manually using the instructions in the README.MD in the
GitHub repository. Once Posh-Git is installed you need to integrate Git into your PowerShell
environment. Posh-Git includes an example profile script that you can adapt to your needs.</p>

<h3 id="integrate-git-into-your-powershell-environment" data-numberify>Integrate Git into your PowerShell environment<a class="anchor ms-1" href="#integrate-git-into-your-powershell-environment"></a></h3>
<p>Integrating Git into PowerShell is simple. There are three main things to do:</p>
<ol>
<li>Load the Posh-Git module</li>
<li>Start the SSH Agent Service</li>
<li>Configure your prompt to show the Git status</li>
</ol>
<p>Add the following lines to your PowerShell profile script.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Import-Module posh-git
</span></span><span style="display:flex;"><span>Start-SshAgent -Quiet
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> global:prompt {
</span></span><span style="display:flex;"><span>    $identity = [<span style="color:#66d9ef">Security.Principal.WindowsIdentity</span>]::GetCurrent()
</span></span><span style="display:flex;"><span>    $principal = [<span style="color:#66d9ef">Security.Principal.WindowsPrincipal</span>] $identity
</span></span><span style="display:flex;"><span>    $name = ($identity.Name -split <span style="color:#e6db74">&#39;\\&#39;</span>)[<span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>    $path = Convert-Path $executionContext.SessionState.Path.CurrentLocation
</span></span><span style="display:flex;"><span>    $prefix = <span style="color:#e6db74">&#34;(</span>$env:PROCESSOR_ARCHITECTURE<span style="color:#e6db74">)&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>($principal.IsInRole([<span style="color:#66d9ef">Security.Principal.WindowsBuiltInRole</span>] <span style="color:#e6db74">&#39;Administrator&#39;</span>)) { $prefix = <span style="color:#e6db74">&#34;Admin: </span>$prefix<span style="color:#e6db74">&#34;</span> }
</span></span><span style="display:flex;"><span>    $realLASTEXITCODE = $LASTEXITCODE
</span></span><span style="display:flex;"><span>    $prefix = <span style="color:#e6db74">&#34;Git </span>$prefix<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    Write-Host (<span style="color:#e6db74">&#34;</span>$prefix<span style="color:#e6db74">[</span>$Name<span style="color:#e6db74">]&#34;</span>) -nonewline
</span></span><span style="display:flex;"><span>    Write-VcsStatus
</span></span><span style="display:flex;"><span>    (<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">`n</span>$(<span style="color:#e6db74">&#39;+&#39;</span> * (get-location -stack).count)<span style="color:#e6db74">&#34;</span>) + <span style="color:#e6db74">&#34;PS </span>$($path)$(<span style="color:#e6db74">&#39;&gt;&#39;</span> * ($nestedPromptLevel + <span style="color:#ae81ff">1</span>))<span style="color:#e6db74"> &#34;</span>
</span></span><span style="display:flex;"><span>    $global:LASTEXITCODE = $realLASTEXITCODE
</span></span><span style="display:flex;"><span>    $host.ui.RawUI.WindowTitle = <span style="color:#e6db74">&#34;</span>$prefix<span style="color:#e6db74">[</span>$Name<span style="color:#e6db74">] </span>$($path)<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The prompt function integrates Git into your PowerShell prompt to show an abbreviated <code>git status</code>.
See the <a href="https://github.com/dahlbyk/posh-git/blob/master/readme.md" target="_blank" rel="noopener noreferrer">README<i class="fas fa-external-link-square-alt ms-1"></i></a> for Posh-Git for a full explanation of the abbreviated status. I have also
customize my prompt to show me my user context, whether I am running in a 64-bit or 32-bit shell,
and if I am running elevated. Customize this function to meet your needs or preferences. At this
point you are done. You can use Git from PowerShell. Go forth and clone a repo.</p>

<h2 id="customize-your-git-environment" data-numberify>Customize your Git environment<a class="anchor ms-1" href="#customize-your-git-environment"></a></h2>
<p>You may want to customize some of the settings of your Git environment, especially if this is a new
install of Git. Being a good project contributor in Git you should identify yourself so that Git
knows who to <code>blame</code> for your commits. Also, I found that the default colors used by Git in the
shell could be hard to read. So I customized the colors to make them more visible. For more
information, see the <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration" target="_blank" rel="noopener noreferrer">Customizing Git<i class="fas fa-external-link-square-alt ms-1"></i></a> topic in the Git documentation. The following commands
only need to be run once. You are setting global preferences so, once they are set, they are used
every time you start a new shell.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#75715e"># Configure your user information to match your GitHub profile</span>
</span></span><span style="display:flex;"><span>git config --global user.name <span style="color:#e6db74">&#34;John Doe&#34;</span>
</span></span><span style="display:flex;"><span>git config --global user.email <span style="color:#e6db74">&#34;alias@example.com&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Set up the colors to improve visibility in the shell</span>
</span></span><span style="display:flex;"><span>git config --global color.ui true
</span></span><span style="display:flex;"><span>git config --global color.status.changed <span style="color:#e6db74">&#34;magenta bold&#34;</span>
</span></span><span style="display:flex;"><span>git config --global color.status.untracked <span style="color:#e6db74">&#34;red bold&#34;</span>
</span></span><span style="display:flex;"><span>git config --global color.status.added <span style="color:#e6db74">&#34;yellow bold&#34;</span>
</span></span><span style="display:flex;"><span>git config --global color.status.unmerged <span style="color:#e6db74">&#34;yellow bold&#34;</span>
</span></span><span style="display:flex;"><span>git config --global color.branch.remote <span style="color:#e6db74">&#34;magenta bold&#34;</span>
</span></span><span style="display:flex;"><span>git config --global color.branch.upstream <span style="color:#e6db74">&#34;blue bold&#34;</span>
</span></span><span style="display:flex;"><span>git config --global color.branch.current <span style="color:#e6db74">&#34;green bold&#34;</span>
</span></span></code></pre></div><p>As I said at the beginning, this is what works for me. Your mileage may vary. Customize this for
your preferences and environmental needs. In future articles, I plan to share scripts I have created
to help me with my Git workflow. Do you use Git with Powershell? Share your questions and
experiences in the comments.</p>
]]></content:encoded></item><item><title>Opening the door to the Mystery of Dates in PowerShell</title><link>https://seanonit.org/blog/2015/11/opening-the-door-to-the-mystery-of-dates-in-powershell/</link><pubDate>Fri, 27 Nov 2015 00:00:00 -0600</pubDate><guid>https://seanonit.org/blog/2015/11/opening-the-door-to-the-mystery-of-dates-in-powershell/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>
</blockquote>
<p>Formatting and converting dates can be very confusing. Every programming language, operating system,
and runtime environment seem to do it differently. And part of the difficulty in conversion is
knowing what units you are starting with. First, it is helpful to know the <a href="https://wikipedia.org/wiki/Epoch_%28reference_date%29" target="_blank" rel="noopener noreferrer">Epoch<i class="fas fa-external-link-square-alt ms-1"></i></a> (or starting
date) a stored value is based on. Wikipedia has a good article on this. Here is a brief excerpt.</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>
</blockquote>
<p>Formatting and converting dates can be very confusing. Every programming language, operating system,
and runtime environment seem to do it differently. And part of the difficulty in conversion is
knowing what units you are starting with. First, it is helpful to know the <a href="https://wikipedia.org/wiki/Epoch_%28reference_date%29" target="_blank" rel="noopener noreferrer">Epoch<i class="fas fa-external-link-square-alt ms-1"></i></a> (or starting
date) a stored value is based on. Wikipedia has a good article on this. Here is a brief excerpt.</p>
<table>
  <thead>
      <tr>
          <th>Epoch date</th>
          <th>Notable uses</th>
          <th>Rationale for selection</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>January 1, AD 1</td>
          <td>Microsoft .NET</td>
          <td>Common Era, ISO 2014, RFC 3339</td>
      </tr>
      <tr>
          <td>January 1, 1601</td>
          <td>NTFS, COBOL, Win32/Win64</td>
          <td>1601 was the first year of the 400-year Gregorian calendar cycle at the time Windows NT was made</td>
      </tr>
      <tr>
          <td>January 0, 1900</td>
          <td>Microsoft Excel, Lotus 1-2-3</td>
          <td>While logically January 0, 1900 is equivalent to December 31, 1899, these systems do not allow users to specify the latter date.</td>
      </tr>
      <tr>
          <td>January 1, 1904</td>
          <td>Apple Inc.&rsquo;s macOS through version 9</td>
          <td>1904 is the first leap year of the 20th century</td>
      </tr>
      <tr>
          <td>January 1, 1970</td>
          <td>Unix Epoch aka POSIX time. Used by Unix and Unix-like systems (Linux, macOS X), and programming languages: most C/C++ implementations, Java, JavaScript, Perl, PHP, Python, Ruby, Tcl, ActionScript.</td>
          <td></td>
      </tr>
      <tr>
          <td>January 1, 1980</td>
          <td>IBM BIOS INT 1Ah, DOS, OS/2, FAT12, FAT16, FAT32, exFAT filesystems</td>
          <td>The IBM PC with its BIOS as well as 86-DOS, MS-DOS and PC DOS with their FAT12 file system were developed and introduced between 1980 and 1981</td>
      </tr>
  </tbody>
</table>

<h2 id="common-date-conversion-tasks" data-numberify>Common Date Conversion Tasks<a class="anchor ms-1" href="#common-date-conversion-tasks"></a></h2>
<ul>
<li>
<p><strong>WMI Dates</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS&gt; $installDate = (Get-WmiObject win32_operatingsystem | select Installdate ).InstallDate
</span></span><span style="display:flex;"><span>PS&gt; [<span style="color:#66d9ef">system.management.managementdatetimeconverter</span>]::ToDateTime($InstallDate)
</span></span><span style="display:flex;"><span>Friday, September <span style="color:#ae81ff">12</span>, <span style="color:#ae81ff">2008</span> <span style="color:#ae81ff">6</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">50</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">57</span> PM
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PS&gt; [<span style="color:#66d9ef">System.Management.ManagementDateTimeConverter</span>]::ToDmtfDateTime($(get-date))
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">20151127144036.886000</span>-<span style="color:#ae81ff">480</span>
</span></span></code></pre></div></li>
<li>
<p><strong>Excel dates</strong> - Excel stores dates as sequential serial numbers so that they can be used in
calculations. By default, January 1, 1900, is serial number 1.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS&gt; ((Get-Date).AddDays(<span style="color:#ae81ff">1</span>) - (get-date <span style="color:#e6db74">&#34;12/31/1899&#34;</span>)).Days
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">42335</span>
</span></span></code></pre></div><p>In this example, the value Days is <code>42335</code> which is the serial number for 11/27/2015 in Excel. The
date <code>12/31/1899</code> is equivalent to January 0, 1900. The difference between <code>12/31/1899</code> and
<code>11/27/2015</code> is <code>42334</code> but since the serial numbers start a 1 you need to add 1 day to get the
serial number for <code>11/27/2015</code>.</p>
</li>
<li>
<p><strong>Converting from custom string formats</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS&gt; $information = <span style="color:#e6db74">&#39;12Nov(2012)18h30m17s&#39;</span>
</span></span><span style="display:flex;"><span>PS&gt; $pattern = <span style="color:#e6db74">&#39;ddMMM\(yyyy\)HH\hmm\mss\s&#39;</span>
</span></span><span style="display:flex;"><span>PS&gt; [<span style="color:#66d9ef">datetime</span>]::ParseExact($information, $pattern, $null)
</span></span><span style="display:flex;"><span>Monday, November <span style="color:#ae81ff">12</span>, <span style="color:#ae81ff">2012</span> <span style="color:#ae81ff">6</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">30</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">17</span> PM
</span></span></code></pre></div></li>
<li>
<p><strong>FILETIME conversion</strong> - FILETIME is a 64-bit value representing the number of 100-nanosecond
intervals since January 1, 1601 (UTC).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS&gt; Get-AdUser username -prop badPasswordTime,lastLogonTimestamp | select badPasswordTime, lastLogonTimestamp
</span></span><span style="display:flex;"><span>badPasswordTime <span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#ae81ff">130927962789982434</span>
</span></span><span style="display:flex;"><span>lastLogonTimestamp <span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#ae81ff">130931333173599571</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PS&gt; [<span style="color:#66d9ef">datetime</span>]::fromfiletime(<span style="color:#ae81ff">130927962789982434</span>)
</span></span><span style="display:flex;"><span>Monday, November <span style="color:#ae81ff">23</span>, <span style="color:#ae81ff">2015</span> <span style="color:#ae81ff">3</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">51</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">18</span> PM
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PS&gt; [<span style="color:#66d9ef">datetime</span>]::fromfiletime(<span style="color:#ae81ff">130931333173599571</span>)
</span></span><span style="display:flex;"><span>Friday, November <span style="color:#ae81ff">27</span>, <span style="color:#ae81ff">2015</span> <span style="color:#ae81ff">1</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">28</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">37</span> PM
</span></span></code></pre></div></li>
<li>
<p><strong>CTIME or Unix format</strong> - is an integral value representing the number of seconds elapsed since
00:00 hours, Jan 1, 1970 UTC (i.e., a Unix timestamp).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS&gt; $epoch = get-date <span style="color:#e6db74">&#34;1/1/1970&#34;</span>
</span></span><span style="display:flex;"><span>PS&gt; $epoch.AddMilliseconds(<span style="color:#ae81ff">1448302797803</span>)
</span></span><span style="display:flex;"><span>Monday, November <span style="color:#ae81ff">23</span>, <span style="color:#ae81ff">2015</span> <span style="color:#ae81ff">6</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">19</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">57</span> PM
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PS&gt; $epoch.AddSeconds(<span style="color:#ae81ff">1448302797.803</span>)
</span></span><span style="display:flex;"><span>Monday, November <span style="color:#ae81ff">23</span>, <span style="color:#ae81ff">2015</span> <span style="color:#ae81ff">6</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">19</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">57</span> PM
</span></span></code></pre></div></li>
</ul>

<h2 id="references" data-numberify>References<a class="anchor ms-1" href="#references"></a></h2>
<ul>
<li><a href="https://docs.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings" target="_blank" rel="noopener noreferrer">Standard Date and Time Format Strings in .NET<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://docs.microsoft.com/dotnet/standard/base-types/custom-date-and-time-format-strings" target="_blank" rel="noopener noreferrer">Custom Date and Time Format Strings in .NET<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://docs.microsoft.com/previous-versions/windows/it-pro/windows-powershell-1.0/ee692801%28v=technet.10%29" target="_blank" rel="noopener noreferrer">Formatting Dates and Times in PowerShell<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://devblogs.microsoft.com/scripting/powertip-use-powershell-to-format-dates/" target="_blank" rel="noopener noreferrer">PowerTip: Use PowerShell to Format Dates<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="http://community.idera.com/powershell/powertips/b/tips/posts/parsing-custom-date-and-time-formats" target="_blank" rel="noopener noreferrer">Parsing Custom Date and Time Formats<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://learn.microsoft.com/dotnet/api/system.datetime.parseexact?view=net-8.0#overloads" target="_blank" rel="noopener noreferrer">ParseExact() method<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li><a href="https://wikipedia.org/wiki/Epoch_%28reference_date%29" target="_blank" rel="noopener noreferrer">Wikipedia - Epoch (reference date)<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
</ul>
]]></content:encoded></item><item><title>Adding a Contact to a Distribution List with PowerShell</title><link>https://seanonit.org/blog/2015/07/adding-a-contact-to-a-distribution-list-with-powershell/</link><pubDate>Tue, 21 Jul 2015 00:00:00 -0500</pubDate><guid>https://seanonit.org/blog/2015/07/adding-a-contact-to-a-distribution-list-with-powershell/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>The PowerShell ActiveDirectory module has a lot of great features that I use on a daily basis.
However, there is one shortcoming that I have struggled with for a while. I did a lot of internet
searching and testing to see if I was missing some hidden secret. But, alas, this is one task that
the AD module does not do. Here is the scenario. We have a lot of AD Groups (Distribution Lists) we
use for notification messages. We want to send notifications to mobile devices. We do this by
sending an email to the devices email address. For example:</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>The PowerShell ActiveDirectory module has a lot of great features that I use on a daily basis.
However, there is one shortcoming that I have struggled with for a while. I did a lot of internet
searching and testing to see if I was missing some hidden secret. But, alas, this is one task that
the AD module does not do. Here is the scenario. We have a lot of AD Groups (Distribution Lists) we
use for notification messages. We want to send notifications to mobile devices. We do this by
sending an email to the devices email address. For example:</p>
<pre tabindex="0"><code>2065551212@mobilecarrier.xyz.com
</code></pre><p>These external email addresses are created as Contact objects in AD. The problem is that the cmdlets
for managing AD group objects only allow you to add objects that have a SamAccountName (and
therefore a SID) to a group. This is fine for user and group objects. But Contact objects to not
have SIDs. So now what do you do. The answer is you do it the old way you would have done it in
VBScript; use ADSI.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>$dlGroup = [<span style="color:#66d9ef">adsi</span>]<span style="color:#e6db74">&#39;LDAP://CN=DL-Group Name,OU=Corp Distribution Lists,DC=contoso,DC=net&#39;</span>
</span></span><span style="display:flex;"><span>$dlGroup.Member.Add(<span style="color:#e6db74">&#39;CN=mobile-username,OU=Corp Contacts,DC=contoso,DC=net&#39;</span>)
</span></span><span style="display:flex;"><span>$dlGroup.psbase.CommitChanges()
</span></span></code></pre></div>]]></content:encoded></item><item><title>Use PowerShell and EWS to find out who is sending you email</title><link>https://seanonit.org/blog/2015/03/use-powershell-and-ews-to-find-out-who-is-sending-you-email/</link><pubDate>Wed, 18 Mar 2015 00:00:00 -0500</pubDate><guid>https://seanonit.org/blog/2015/03/use-powershell-and-ews-to-find-out-who-is-sending-you-email/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>I get a lot of email from a lot of different sources. A lot of it is from automated alerts generated
by services accounts that monitor various applications that my team supports. Each month I like to
see how many messages I have gotten from the various sources. Looking at these numbers over time can
be helpful to identify trends. If we are suddenly getting more alerts from a particular sender then
we may want to look more closely at the health of that system. Using Outlook&rsquo;s rules engine I send
all of these alert messages to a specific folder. Now I just need an easy way to count them. I
created a script that scans that folder and counts the number of messages from each sender. The
output looks like this:</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>I get a lot of email from a lot of different sources. A lot of it is from automated alerts generated
by services accounts that monitor various applications that my team supports. Each month I like to
see how many messages I have gotten from the various sources. Looking at these numbers over time can
be helpful to identify trends. If we are suddenly getting more alerts from a particular sender then
we may want to look more closely at the health of that system. Using Outlook&rsquo;s rules engine I send
all of these alert messages to a specific folder. Now I just need an easy way to count them. I
created a script that scans that folder and counts the number of messages from each sender. The
output looks like this:</p>
<pre tabindex="0"><code>Count Name
----- ----
   10 Service Account A &lt;SMTP:svca@contoso.com&gt;
   10 Ops Monitor 2 &lt;SMTP:opsmon2@contoso.com&gt;
    7 Ops Monitor 3 &lt;SMTP:opsmon3@contoso.com&gt;
    6 Service Account D &lt;SMTP:svcd@contoso.com&gt;
    6 Service Account E &lt;SMTP:svce@contoso.com&gt;
</code></pre><p>The script is pretty simple. I created two functions:</p>
<ul>
<li>one to find the specific folder in the mailbox</li>
<li>one to iterate through all the items in the folder</li>
</ul>
<p>To find the target folder you must walk the folder tree until you reach your destination. Once you
have the target folder you can create an ItemView and search for all the messages in the folder.
PowerShell&rsquo;s <code>Group-Object</code> cmdlet does the work of counting for you.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#75715e"># Filename: get-sendercount.ps1</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Load the EWS dll</span>
</span></span><span style="display:flex;"><span>Add-Type -Path <span style="color:#e6db74">&#39;C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#-----------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> GetTargetFolder {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">param</span>([<span style="color:#66d9ef">string</span>]$folderPath)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   $fldArray = $folderPath.Split(<span style="color:#e6db74">&#34;\&#34;</span>)
</span></span><span style="display:flex;"><span>   $tfTargetFolder = $MsgRoot
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">for</span> ($x = <span style="color:#ae81ff">1</span>; $x <span style="color:#f92672">-lt</span> $fldArray.Length; $x++)
</span></span><span style="display:flex;"><span>   {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">#$fldArray[$x]</span>
</span></span><span style="display:flex;"><span>      $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>      $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo(
</span></span><span style="display:flex;"><span>         [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.FolderSchema</span>]::DisplayName,
</span></span><span style="display:flex;"><span>         $fldArray[$x]
</span></span><span style="display:flex;"><span>      )
</span></span><span style="display:flex;"><span>      $findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> ($findFolderResults.TotalCount <span style="color:#f92672">-gt</span> <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>         <span style="color:#66d9ef">foreach</span>($folder <span style="color:#66d9ef">in</span> $findFolderResults.Folders)
</span></span><span style="display:flex;"><span>         {
</span></span><span style="display:flex;"><span>             $tfTargetFolder = $folder
</span></span><span style="display:flex;"><span>         }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>         <span style="color:#e6db74">&#34;Error Folder Not Found&#34;</span>
</span></span><span style="display:flex;"><span>         $tfTargetFolder = $null
</span></span><span style="display:flex;"><span>         <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>   $tfTargetFolder
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">#-----------------------------------------------------</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> GetItems {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">param</span> ($targetFolder)
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">#Define ItemView to retrive just 100 Items at a time</span>
</span></span><span style="display:flex;"><span>   $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(<span style="color:#ae81ff">100</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   $AQSString = $null  <span style="color:#75715e">#find all messages</span>
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>   {
</span></span><span style="display:flex;"><span>        $fiItems = $service.FindItems($targetFolder.Id,$AQSString,$ivItemView)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">foreach</span>($Item <span style="color:#66d9ef">in</span> $fiItems.Items)
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            $Item.Load()
</span></span><span style="display:flex;"><span>            $Item
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        $ivItemView.Offset += $fiItems.Items.Count
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">while</span>($fiItems.MoreAvailable <span style="color:#f92672">-eq</span> $true)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">#-----------------------------------------------------</span>
</span></span><span style="display:flex;"><span>$ExchangeVersion = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeVersion</span>]::Exchange2010_SP2
</span></span><span style="display:flex;"><span>$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$service.UseDefaultCredentials = $true
</span></span><span style="display:flex;"><span>$MailboxName = <span style="color:#e6db74">&#34;mymailbox@contoso.com&#34;</span>
</span></span><span style="display:flex;"><span>$service.AutodiscoverUrl($MailboxName)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#Bind to the Root of the mailbox so I can search the folder namespace for the target</span>
</span></span><span style="display:flex;"><span>$MsgRootId = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.WellKnownFolderName</span>]::MsgFolderRoot
</span></span><span style="display:flex;"><span>$MsgRoot = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.Folder</span>]::Bind($service,$MsgRootId)
</span></span><span style="display:flex;"><span>$targetFolder = GetTargetFolder <span style="color:#e6db74">&#39;\Inbox\Alert Message\Current&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$itemList = GetItems $targetFolder
</span></span><span style="display:flex;"><span>$itemList | group-object Sender -noelement | sort Count -desc | ft -a
</span></span></code></pre></div>]]></content:encoded></item><item><title>Understanding Byte Arrays in PowerShell</title><link>https://seanonit.org/blog/2014/11/understanding-byte-arrays-in-powershell/</link><pubDate>Tue, 11 Nov 2014 00:00:00 -0600</pubDate><guid>https://seanonit.org/blog/2014/11/understanding-byte-arrays-in-powershell/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>In a previous <a href="../2014-11-03/">article</a>, I presented a PowerShell script for inspecting and validating
certificates stored as PFX files. My goal is to get data into an X509Certificate2 object so that I
can validate the certificate properties. The <a href="https://learn.microsoft.com/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.import#overloads" target="_blank" rel="noopener noreferrer">X509Certificate2 Import() methods<i class="fas fa-external-link-square-alt ms-1"></i></a> have two sets
variations. One set takes a filename for the certificate file to be imported. The second set takes a
Byte array containing the certificate data to be imported.</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>In a previous <a href="../2014-11-03/">article</a>, I presented a PowerShell script for inspecting and validating
certificates stored as PFX files. My goal is to get data into an X509Certificate2 object so that I
can validate the certificate properties. The <a href="https://learn.microsoft.com/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.import#overloads" target="_blank" rel="noopener noreferrer">X509Certificate2 Import() methods<i class="fas fa-external-link-square-alt ms-1"></i></a> have two sets
variations. One set takes a filename for the certificate file to be imported. The second set takes a
Byte array containing the certificate data to be imported.</p>
<p>In this script, I can import PFX certificate files by downloading a byte stream from a web server or
by reading a file stored on disk. I want to avoid creating temporary files and I want to make a
generic import function that could be used independently of the data retrieval method. I settled on
using an array of bytes as the import format for either scenario.</p>
<p>To import a PFX file from disk I use the <a href="https://learn.microsoft.com/powershell/module/Microsoft.PowerShell.Management/Get-Content" target="_blank" rel="noopener noreferrer">Get-Content<i class="fas fa-external-link-square-alt ms-1"></i></a> cmdlet. Let&rsquo;s take a closer look at how
Get-Content works and what it returns.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes = Get-Content .\DEV113.pfx
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes.GetType().Name
</span></span><span style="display:flex;"><span>Object[]
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes[<span style="color:#ae81ff">0</span>].GetType().Name
</span></span><span style="display:flex;"><span>String
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes[<span style="color:#ae81ff">0</span>].length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">18</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes.length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">70</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes | ForEach-Object { $count += $_.length }
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $count
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">7489</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; Get-ChildItem .\DEV113.pfx
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Directory<span style="color:#960050;background-color:#1e0010">:</span> C:\temp
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Mode              LastWriteTime     Length Name
</span></span><span style="display:flex;"><span>----              -------------     ------ ----
</span></span><span style="display:flex;"><span>-a---       <span style="color:#ae81ff">9</span>/<span style="color:#ae81ff">30</span>/<span style="color:#ae81ff">2014</span>   <span style="color:#ae81ff">1</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">03</span> PM       <span style="color:#ae81ff">7558</span> DEV113.pfx
</span></span></code></pre></div><p>By default, we see that <code>Get-Content</code> returns an array of String objects. There are two problems
with this for my use case.</p>
<ol>
<li>If you add up the length of all 70 strings you get a total of 7489 characters. But the files size
is 7558 bytes, so this does not match. The data in a PFX files is not string-oriented. It is
binary data.</li>
<li>I need a Byte array to import the data into an X509Certificate2 object.</li>
</ol>
<p>Fortunately, using the <strong>Encoding</strong> parameter you can specify that you want Byte encoded data
returned instead of strings.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes = Get-Content .\DEV113.pfx -Encoding Byte
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes.GetType().Name
</span></span><span style="display:flex;"><span>Object[]
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes[<span style="color:#ae81ff">0</span>].GetType().Name
</span></span><span style="display:flex;"><span>Byte
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes[<span style="color:#ae81ff">0</span>].length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $pfxbytes.length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">7558</span>
</span></span></code></pre></div><p>Notice that Get-Content still returns an array of objects but those objects are Bytes. The total
length of <code>$pfxbytes</code> now matches the size on disk. To download the PFX file from the web server I
am using the <strong>System.Net.Webclient</strong> class. <strong>System.Net.Webclient</strong> has three main ways of
downloading content from a web server:</p>
<ul>
<li>The <a href="https://learn.microsoft.com/dotnet/api/system.net.webclient.downloadstring?view=net-8.0#System_Net_WebClient_DownloadString_System_String_" target="_blank" rel="noopener noreferrer">DownloadString<i class="fas fa-external-link-square-alt ms-1"></i></a> methods are useful when you are only expecting to receive text data (e.g.
HTML, XML, or JSON). Since the PFX file format is binary, not text, this will not work as I have
already shown above with <code>Get-Content</code>.</li>
<li>The <a href="https://learn.microsoft.com/dotnet/api/system.net.webclient.downloadfile?view=net-8.0#System_Net_WebClient_DownloadFile_System_String_System_String_" target="_blank" rel="noopener noreferrer">DownloadFile<i class="fas fa-external-link-square-alt ms-1"></i></a> methods would work except that I don&rsquo;t want to have to save the file to
disk as required by these methods.</li>
<li>The <a href="https://learn.microsoft.com/dotnet/api/system.net.webclient.downloaddata?view=net-8.0#System_Net_WebClient_DownloadData_System_String_" target="_blank" rel="noopener noreferrer">DownloadData<i class="fas fa-external-link-square-alt ms-1"></i></a> methods return a byte array containing the data requested. This is the
method that best meets our needs.</li>
</ul>
<p><strong>But what is a Byte array? How is a Byte array different than a string?</strong></p>
<p>A byte array can contain any arbitrary binary data. The data does not have to be character data.
Character data is subject to interpretation. Character data implies encoding. There is more than one
way to encode a character. Take the following example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS C:\temp&gt; $string = <span style="color:#e6db74">&#39;Hello World&#39;</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $string.length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">11</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $bytes = [<span style="color:#66d9ef">System.Text.Encoding</span>]::Unicode.GetBytes($string)
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $bytes.length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">22</span>
</span></span></code></pre></div><p>As you can see, the length of <code>$string</code> is 11 characters. If we convert that to a <strong>byte[]</strong> we get
22 bytes of data. It is also important to know the format of the source data when you are converting
between encoding schemes. Take for example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS C:\temp&gt; $array = @(<span style="color:#ae81ff">72</span>,<span style="color:#ae81ff">101</span>,<span style="color:#ae81ff">108</span>,<span style="color:#ae81ff">108</span>,<span style="color:#ae81ff">111</span>,<span style="color:#ae81ff">32</span>,<span style="color:#ae81ff">87</span>,<span style="color:#ae81ff">111</span>,<span style="color:#ae81ff">114</span>,<span style="color:#ae81ff">108</span>,<span style="color:#ae81ff">100</span>)
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $string = [<span style="color:#66d9ef">System.Text.Encoding</span>]::UTF8.GetString($array)
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $string.length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">11</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $string
</span></span><span style="display:flex;"><span>Hello World
</span></span></code></pre></div><p>You see it is possible to convert the <strong>byte[]</strong> <code>$array</code> to a UTF8 encoded string because each byte
represents one character. However, if you try to convert that same array to Unicode it will treat
each pair of bytes as a single character.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>PS C:\temp&gt; $string = [<span style="color:#66d9ef">System.Text.Encoding</span>]::Unicode.GetString($array)
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $string.length
</span></span><span style="display:flex;"><span><span style="color:#ae81ff">6</span>
</span></span><span style="display:flex;"><span>PS C:\temp&gt; $string
</span></span><span style="display:flex;"><span>??????
</span></span></code></pre></div><p>The result is an unreadable value stored in <code>$string</code>.</p>
]]></content:encoded></item><item><title>Throwback Thursday - Windows Command Shell (Batch) scripting</title><link>https://seanonit.org/blog/2014/11/throwback-thursday-windows-command-shell-batch-scripting/</link><pubDate>Thu, 06 Nov 2014 00:00:00 -0600</pubDate><guid>https://seanonit.org/blog/2014/11/throwback-thursday-windows-command-shell-batch-scripting/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>This Thursday I am returning to my scripting roots (if you don&rsquo;t count <a href="http://en.wikipedia.org/wiki/DIGITAL_Command_Language" target="_blank" rel="noopener noreferrer">VAX DCL<i class="fas fa-external-link-square-alt ms-1"></i></a>) to talk about
Windows Command Shell scripts. With nice powerful scripting options like PowerShell why does anyone
bother with &ldquo;DOS Batch&rdquo; scripts anymore?</p>
<p>First off, let me set the record straight. Windows Command Shell scripting is much more powerful
than &ldquo;DOS Batch&rdquo; files. Yes, they share a common heritage and syntax. But the Windows Command shell
can do so much more. Not to mention, there are lots of older systems still deployed that don&rsquo;t have
PowerShell installed. The Windows Command shell is guaranteed to be installed.</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>This Thursday I am returning to my scripting roots (if you don&rsquo;t count <a href="http://en.wikipedia.org/wiki/DIGITAL_Command_Language" target="_blank" rel="noopener noreferrer">VAX DCL<i class="fas fa-external-link-square-alt ms-1"></i></a>) to talk about
Windows Command Shell scripts. With nice powerful scripting options like PowerShell why does anyone
bother with &ldquo;DOS Batch&rdquo; scripts anymore?</p>
<p>First off, let me set the record straight. Windows Command Shell scripting is much more powerful
than &ldquo;DOS Batch&rdquo; files. Yes, they share a common heritage and syntax. But the Windows Command shell
can do so much more. Not to mention, there are lots of older systems still deployed that don&rsquo;t have
PowerShell installed. The Windows Command shell is guaranteed to be installed.</p>
<p>Some things you can do in Windows command shell scripts that you may not have known:</p>
<ul>
<li>Arithmetic (using the SET /A command)</li>
<li>Complex FOR loops (parsing, counting, collection enumeration)</li>
<li>Subroutines within a single script file (using &ldquo;CALL :label&rdquo;)</li>
<li>Text parsing (using the FOR command)</li>
<li>Additional built-in environment variables (e.g. %CD%, %RANDOM%, and others)</li>
<li>Variable Substring extraction and replacement (see the help for the SET command)</li>
<li>Variable value transformation (e.g. get the size of the file named by the variable using <code>%\~z1</code>)</li>
</ul>
<p>I will illustrate a view of these enhancements while I discuss different ways of handling command
line arguments.</p>
<p>The Windows Command shell provide variable for the first nine arguments passed on the command line
when executing a script. These variables are numbered %1 through %9. But what if you want (or need)
to pass more parameters than that? Take the following script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cmd" data-lang="cmd"><span style="display:flex;"><span>@<span style="color:#66d9ef">echo</span> off
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[1] = %1
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[2] = %2
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[3] = %3
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[4] = %4
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[5] = %5
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[6] = %6
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[7] = %7
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[8] = %8
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[9] = %9
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[10] = %10
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[11] = %11
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[12] = %12
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[13] = %13
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[14] = %14
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[15] = %15
</span></span></code></pre></div><p>Let&rsquo;s see what happens when you try to access the 10<!-- raw HTML omitted -->th<!-- raw HTML omitted --> argument:</p>
<pre tabindex="0"><code>C:\temp&gt; test15args.cmd a b c d e f g h i j k l m n o
Arg[1] = a
Arg[2] = b
Arg[3] = c
Arg[4] = d
Arg[5] = e
Arg[6] = f
Arg[7] = g
Arg[8] = h
Arg[9] = i
Arg[10] = a0
Arg[11] = a1
Arg[12] = a2
Arg[13] = a3
Arg[14] = a4
Arg[15] = a5
</code></pre><p>Notice that starting with the 10 argument the script is just outputting the value of the
1<!-- raw HTML omitted -->st<!-- raw HTML omitted --> argument followed by a number. Also, using the numbered variables implies that you
expect command line arguments to be passed in a specific order. What if you want to pass arguments
in any order or handle more than nine values? This is where the SHIFT command comes in. SHIFT is not
new. It existed in DOS prior to the Windows Command shell, but when combined with other new features
of the Command shell it is more powerful. Take this next example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cmd" data-lang="cmd"><span style="display:flex;"><span>@<span style="color:#66d9ef">echo</span> off
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">setlocal</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">set</span> <span style="color:#66d9ef">/a</span> c<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>:top
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Arg[%c%] = <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">set</span> <span style="color:#66d9ef">/a</span> c<span style="color:#f92672">+=</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">shift</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">NEQ</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#66d9ef">goto</span> :top
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> You should never reach this line of the script.
</span></span></code></pre></div><p>SHIFT allows us to access each argument by SHIFT-ing it through the %1 variable. Bonus: notice the
use of &ldquo;set /a&rdquo;. This is how you do arithmetic. Here is the output:</p>
<pre tabindex="0"><code>C:\temp&gt; testAllargs.cmd a b c d e f g h i j k l m n o
Arg[1] = &#34;a&#34;
Arg[2] = &#34;b&#34;
Arg[3] = &#34;c&#34;
Arg[4] = &#34;d&#34;
Arg[5] = &#34;e&#34;
Arg[6] = &#34;f&#34;
Arg[7] = &#34;g&#34;
Arg[8] = &#34;h&#34;
Arg[9] = &#34;i&#34;
Arg[10] = &#34;j&#34;
Arg[11] = &#34;k&#34;
Arg[12] = &#34;l&#34;
Arg[13] = &#34;m&#34;
Arg[14] = &#34;n&#34;
Arg[15] = &#34;o&#34;
</code></pre><p>So now we have a method of looking at each command line argument and handling it independent of its
position on the command line. Let&rsquo;s look at a more complex script.</p>
<p>I would like to check all the command line arguments and determine if they are valid for the script
before ever trying to execute the main logic of the script. It would also be nice to separate blocks
of code in the script into &ldquo;subroutines&rdquo; so that the complex logic of a specific task can be
isolated from the main logic flow of the script. Here is a high-level outline of such a script.</p>
<pre tabindex="0"><code>  ForEach arg in args[]
  {
    if valid(arg) then add arg to collection
    if not valid(arg) then display error message
  }
  ForEach arg in collection
  {
    call subroutine to handle arg
  }
</code></pre><p>Take a look at the script. The script is divided into four sections:</p>
<ul>
<li>Initialization - where we validate the arguments</li>
<li>Main block - where we control the order of execution</li>
<li>Subroutines - where the actual work gets done</li>
<li>Helper functions - specialized tasks that are not part of the main logic</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cmd" data-lang="cmd"><span style="display:flex;"><span>@<span style="color:#66d9ef">echo</span> off
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">setlocal</span> EnableDelayedExpansion
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:GetOpts<span style="color:#75715e"> - Check Command line options</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;&#34;</span> (
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">goto</span> :FinInit
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION01&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION02&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION03&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION04&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION05&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION06&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION07&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION08&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION09&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION10&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION11&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION12&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION13&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION14&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#66d9ef">/i</span> <span style="color:#e6db74">&#34;</span>%1<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">EQU</span> <span style="color:#e6db74">&#34;ACTION15&#34;</span> <span style="color:#66d9ef">goto</span> :AddOpt
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:   Fall<span style="color:#75715e"> through to error if no match</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :CMDError<span style="color:#75715e"> %1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:AddOpt
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">set</span> MY_OPT_LIST=!MY_OPT_LIST! %1
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:NextOpt
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">shift</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :GetOpts
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:FinInit<span style="color:#75715e">  Finish initializing script environment</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Finish initializing script environment.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add commands here to create temp files or other tasks required by the script.
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:         Count<span style="color:#75715e"> the number of options</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">set</span> MY_OPT_LIST
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">set</span> <span style="color:#66d9ef">/a</span> optcnt <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#ae81ff">%%</span>a <span style="color:#66d9ef">in</span> (%MY_OPT_LIST%) <span style="color:#66d9ef">do</span> (
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">set</span> <span style="color:#66d9ef">/a</span> optcnt <span style="color:#f92672">=</span> <span style="color:#f92672">!</span>optcnt<span style="color:#f92672">!</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Total options selected = %optcnt%
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:        End<span style="color:#75715e"> of Init section</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:Main<span style="color:#75715e">    Main Section - Process selected options</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> %optcnt% <span style="color:#f92672">EQU</span> 0 <span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#ae81ff">%%</span>a <span style="color:#66d9ef">in</span> (%MY_OPT_LIST%) <span style="color:#66d9ef">do</span> (
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">call</span> :%%a
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:         End<span style="color:#75715e"> of Main section</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:         Begin<span style="color:#75715e"> Action subroutines</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:ACTION01
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action01 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION02
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action02 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION03
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action03 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION04
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action04 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION05
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action05 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION06
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action06 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION07
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action07 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION08
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action08 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION09
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action09 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION10
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action10 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION11
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action11 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION12
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action12 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION13
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action13 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION14
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action14 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:ACTION15
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Add your Action15 commands here.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :eof
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:         End<span style="color:#75715e"> Action subroutines section</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:         Begin<span style="color:#75715e"> Helper functions</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:CMDError<span style="color:#75715e"> - report error in command line options</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Error: &#39;%1&#39; is not a valid option
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span>.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> Valid options are:
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span>    ACTION01, ACTION02, ACTION03, ACTION04, ACTION05,
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span>    ACTION06, ACTION07, ACTION08, ACTION09, ACTION10,
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span>    ACTION11, ACTION12, ACTION13, ACTION14, ACTION15
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span>.
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">goto</span> :EOF
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span><span style="display:flex;"><span>:        End<span style="color:#75715e"> of Script</span>
</span></span><span style="display:flex;"><span>:<span style="color:#75715e">:::::::::::::::::::::::::::::::::::::::::</span>
</span></span></code></pre></div><p>Here is the example output:</p>
<pre tabindex="0"><code>C:\temp&gt; cmdparams.cmd action01 action05 action12 action03
Finish initializing script environment.
Add commands here to create temp files or other tasks required by the script.
MY_OPT_LIST= action01 action05 action12 action03
Total options selected = 4
Add your Action01 commands here.
Add your Action05 commands here.
Add your Action12 commands here.
Add your Action03 commands here.

C:\temp&gt; cmdparams.cmd action1
Error: &#39;action1&#39; is not a valid option

Valid options are:
   ACTION01, ACTION02, ACTION03, ACTION04, ACTION05,
   ACTION06, ACTION07, ACTION08, ACTION09, ACTION10,
   ACTION11, ACTION12, ACTION13, ACTION14, ACTION15
</code></pre><p>OK, what makes a subroutine in Windows Command shell? In DOS Batch you had to put subroutines in a
separate Batch file so you could CALL that batch script from your main script. The Windows Command
shell added functionality to the CALL command that allows you to CALL to a label located in the
current script file instead of using GOTO. The Command shell also added the special label &ldquo;:EOF&rdquo; to
indicate that you were done executing and you want to return back to the caller. This allows you to
separate the &ldquo;business logic&rdquo; of your scripts into subroutines and it them separate from the &ldquo;flow
logic&rdquo; of the main portion of the script.</p>
<p>PS: this is a shout out to <a href="http://www.cse.unt.edu/~donr" target="_blank" rel="noopener noreferrer">DonR<i class="fas fa-external-link-square-alt ms-1"></i></a> who got me started in a lot of scripting languages way back in
1987.</p>
]]></content:encoded></item><item><title>Working with certificates in PowerShell</title><link>https://seanonit.org/blog/2014/11/working-with-certificates-in-powershell/</link><pubDate>Mon, 03 Nov 2014 00:00:00 -0600</pubDate><guid>https://seanonit.org/blog/2014/11/working-with-certificates-in-powershell/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>Today&rsquo;s script is an attempt to bring together several things I have learned about writing good
PowerShell scripts. I still have a lot to learn and this is not necessarily a sterling example of
best practices. However, it does illustrate some more advanced scripting topics, including:</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>Today&rsquo;s script is an attempt to bring together several things I have learned about writing good
PowerShell scripts. I still have a lot to learn and this is not necessarily a sterling example of
best practices. However, it does illustrate some more advanced scripting topics, including:</p>
<ul>
<li>Comment-based help</li>
<li>Parameter sets</li>
<li>Parameters passing on the pipeline</li>
<li>Error handling with try-catch blocks</li>
<li>Simple HTTP downloads using System.Net.Webclient</li>
<li>Inspecting certificates imported from external sources (http or file)</li>
<li>Inspecting certificates in the local store</li>
</ul>
<p>I rarely use comment-based help in my scripts since I am usually writing scripts for my own use.
They tend to be one-off utilities designed to fulfill an immediate need. This script, however, is
going to be used by other support technicians outside of my immediate team. So documentation was
important. Comment-based help allows you to include documentation in the script (rather than a
separate file that can get lost or out of date). And it gives help in a format that users expect for
any other PowerShell command.</p>
<p>Parameter handling in PowerShell is extremely versatile. Through the advanced parameter options, you
can create parameter sets, specify which parameters are mandatory, perform data validation, define
input from the pipeline, and much more. All of this controlled via parameter definition. No need to
write code to validate parameters or ensure valid parameter combinations. PowerShell does the heavy
lifting for you.</p>
<p>My focus will be on the certificate management portions of the script and to outline the scenario
that this script is attempting to support.</p>

<h2 id="the-scenario" data-numberify>The Scenario<a class="anchor ms-1" href="#the-scenario"></a></h2>
<p>We have a set of devices that require a device-specific certificate to be installed. We have a
scripted process for creating, publishing, and installing these certificates. The certificates are
created in bulk for a large number of devices. These certificates are then exported to PFX files
copied to a folder shared by a web server. The device can then download the PFX file and import it
into local certificate store on the device. The devices and the certificates have a standardized
naming scheme (e.g. DEV###). This makes it easy to identify which certificate belongs to which
device.</p>
<p>The certificate lifecycle an unmanaged process. There is no policy mechanism to ensure that the
device has installed the proper certificate or that the certificate installed is correct and valid.
Occasionally we can have problems where the installed certificate is not working properly or the PFX
file published to the web server does not match the certificated issued by the CA. To troubleshoot
these issues we need to be able to verify the certificates on the device in PFX files published on
the web server.</p>

<h2 id="the-solution" data-numberify>The solution<a class="anchor ms-1" href="#the-solution"></a></h2>
<p>This script looks for certificates in one of three locations: the certificate broker (web server),
the local certificate store, or PFX files stored in the file system. In all cases, the output is the
same for each certificate found. The script displays some basic information about the certificate
and then checks that each certificate in the validity chain is still valid.</p>

<h3 id="example-1---check-the-published-pfx-file-for-a-device" data-numberify>Example 1 - check the published PFX file for a device<a class="anchor ms-1" href="#example-1---check-the-published-pfx-file-for-a-device"></a></h3>
<p>This was the first scenario I needed to solve for. The script takes the specified device name and
attempts to download the matching PFX file from the certificate broker.</p>
<pre tabindex="0"><code>PS C:\&gt; .\check-devicecert.ps1 -devices DEV101
</code></pre><p>You can specify one or more device IDs as an array of strings.</p>

<h3 id="example-2---search-for-the-device-certificate-in-the-local-store" data-numberify>Example 2 - search for the device certificate in the local store<a class="anchor ms-1" href="#example-2---search-for-the-device-certificate-in-the-local-store"></a></h3>
<p>The script takes the specified device name and searches for a certificate with a matching Subject
name in the local certificate store.</p>
<pre tabindex="0"><code>PS C:\&gt; .\check-devicecert.ps1 -devices DEV101 -local
</code></pre><p>You can specify one or more device IDs as an array of strings.</p>

<h3 id="example-3---load-a-pfx-file-from-disk" data-numberify>Example 3 - load a PFX file from disk<a class="anchor ms-1" href="#example-3---load-a-pfx-file-from-disk"></a></h3>
<p>The script loads the specified PFX file(s) from disk.</p>
<pre tabindex="0"><code>PS C:\&gt; .\check-devicecert.ps1 -pfxfilename .\DEV113.pfx
PS C:\&gt; dir *.pfx | .\check-devicecert.ps1
</code></pre><p>You can specify one or more PFX filenames as an array of strings. You can also pass an array of
files on the pipeline.</p>
<p>For examples #1 and #3 we are working with PFX files. The first step is to obtain the contents of
the PFX file as an array of bytes so that we can create an X.509 certificate object. To download the
PFX file from the certificate broker we do the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>$client = New-Object -TypeName System.Net.WebClient
</span></span><span style="display:flex;"><span>$url = <span style="color:#e6db74">&#34;https://certbroker.contoso.com/pfxshare/</span>$($num)<span style="color:#e6db74">.pfx&#34;</span>
</span></span><span style="display:flex;"><span>$pfxBytes = $client.DownloadData($url)
</span></span></code></pre></div><p>The <code>DownloadData()</code> method of System.Net.WebClient does this nicely for us.</p>
<p>To load a PFX file from disk I use the Get-Content cmdlet and specify that I want Byte encoding.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>$pfxBytes = Get-Content -path $file -encoding Byte -ErrorAction:SilentlyContinue
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ($error.count <span style="color:#f92672">-ne</span> <span style="color:#ae81ff">0</span>) {<span style="color:#66d9ef">throw</span> $error}
</span></span></code></pre></div><p>Also, note the <strong>ErrorAction</strong> parameter. For some reason, exceptions occurring inside
of <code>Get-Content</code> were not being caught by my Try-Catch block. I had to override the
ErrorAction to force <code>Get-Content</code> to continue silently, check to see if an error
occurred, then re-throw the exception so that it would get caught by my <code>Try/Catch</code> block.</p>
<p>Once I had the Byte array containing the PFX-formatted data blob I needed to import it into an X.509
certificate object.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> import-pfxbytes {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">param</span>($pfxBytes)
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">## Import cert into a new object. No need to import it into a certificate device.</span>
</span></span><span style="display:flex;"><span>  $pfxPass = <span style="color:#e6db74">&#39;pFxP@$5w0rd&#39;</span>
</span></span><span style="display:flex;"><span>  $X509Cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
</span></span><span style="display:flex;"><span>  $X509Cert.Import([<span style="color:#66d9ef">byte[]</span>]$pfxBytes, $pfxPass,<span style="color:#e6db74">&#34;Exportable,PersistKeySet&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> $X509Cert
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>import-pfxbytes</code> function creates an empty X.509 certificate and imports the data using a
static password and returns a certificate object. In this case, I have hard-coded the password. For
better security, you should prompt the user to enter a password (for example, using
<code>Read-Host -AsSecureString</code>).</p>
<p>For example #2 I am using PowerShell&rsquo;s built-in provider to access the local certificate store. With
this access method, you receive a certificate object, not a PFX-formatted data blob. Once I have an
X.509 certificate object I pass it to <code>show-certinfo</code> to inspect the important properties and verify
the validity of the trust chain.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#75715e"># title=&#34;Check-DeviceCert.ps1&#34;]</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;#
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.</span><span style="color:#e6db74">SYNOPSIS</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">Checks a device certificate for validity.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.</span><span style="color:#e6db74">DESCRIPTION</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">The script downloads a device certificate PFX file from the cert broker or reads an existing PFX file then checks for the validity.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.PARAMETER devices
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">An array of device numbers .PARAMETER local Indicates that you want to search the local certificate device.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.PARAMETER pfxfiles
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">An array of pathnames to PFX files deviced on disk. .INPUTS You can provide an array of PFX file names in the pipeline.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.</span><span style="color:#e6db74">EXAMPLE</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">PS C:\&gt; .\check-devicecert.ps1 -devices DEV101
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Downloading DEV101.pfx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Subject      : CN=DEV101@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Issuer       : CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotBefore    : 5/2/2013 12:39:58 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotAfter     : 5/1/2017 12:39:58 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    SerialNumber : 27DC85E200060000B6D2
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Validating certficate chain...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Valid   Certificate
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    -----   -----------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=DEV101@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Root CA, O=CONTOSO
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">The example above illustrates downloading the PFX file from the certificate broker and check the validity.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.</span><span style="color:#e6db74">EXAMPLE</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">PS C:\&gt; .\check-devicecert.ps1 -devices DEV369,DEV123
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Downloading DEV369.pfx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Subject      : CN=DEV369@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Issuer       : CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotBefore    : 5/2/2013 3:37:14 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotAfter     : 5/1/2017 3:37:14 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    SerialNumber : 287ED09B00060000CD63
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Validating certficate chain...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Valid   Certificate
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    -----   -----------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=DEV369@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Root CA, O=CONTOSO
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Downloading DEV123.pfx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Error downloading S123456 - The remote server returned an error: (404) Not Found.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">The example above illustrates downloading multiple PFX files from the certificate broker and check their validity.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.</span><span style="color:#e6db74">EXAMPLE</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">PS C:\temp&gt; .\check-devicecert.ps1 -devices DEV101 -local
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Reading Cert:LocalMachine\My\584C772D4E9EAA9F5858742B2AE4F3E9A0D602C7
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Subject      : CN=DEV101@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Issuer       : CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotBefore    : 5/2/2013 12:39:58 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotAfter     : 5/1/2017 12:39:58 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    SerialNumber : 27DC85E200060000B6D2
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Validating certficate chain...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Valid   Certificate
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    -----   -----------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=DEV101@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Root CA, O=CONTOSO
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">The example above searches for a certificate in the local certificate device and test the validity.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.</span><span style="color:#e6db74">EXAMPLE</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">PS C:\temp&gt; .\check-devicecert.ps1 -pfxfilename .\DEV113.pfx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Reading .\S10113.pfx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Subject      : CN=DEV113@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Issuer       : CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotBefore    : 5/2/2013 3:30:35 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotAfter     : 5/1/2017 3:30:35 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    SerialNumber : 2878BAEA00060000CCAD
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Validating certficate chain...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Valid   Certificate
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    -----   -----------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=DEV113@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Root CA, O=CONTOSO
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">The example above checks the validity of an existing, locally-deviced PFX file.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">.</span><span style="color:#e6db74">EXAMPLE</span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">PS C:\temp&gt; dir *.pfx | .\check-devicecert.ps1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Reading C:\temp\DEV113.pfx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Subject      : CN=DEV113@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Issuer       : CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotBefore    : 5/2/2013 3:30:35 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    NotAfter     : 5/1/2017 3:30:35 PM
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    SerialNumber : 2878BAEA00060000CCAD
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Validating certficate chain...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    Valid   Certificate
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    -----   -----------
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=DEV113@contoso.com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Enterprise CA 02, DC=contoso, DC=com
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    True    CN=Contoso Corporate Root CA, O=CONTOSO
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    ==================================================
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">The example above checks the validity of all the PFX files deviced in folder.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[CmdletBinding(<span style="color:#a6e22e">DefaultParametersetName</span>=<span style="color:#e6db74">&#34;devices&#34;</span>)]
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">param</span> (
</span></span><span style="display:flex;"><span>       [parameter(<span style="color:#a6e22e">ParameterSetName</span>=<span style="color:#e6db74">&#34;names&#34;</span>,<span style="color:#a6e22e">Position</span>=<span style="color:#ae81ff">0</span>,<span style="color:#a6e22e">Mandatory</span>=$true,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ValueFromPipeline</span>=$false,<span style="color:#a6e22e">HelpMessage</span>=<span style="color:#e6db74">&#34;Enter device Number, Ex S12345&#34;</span>)]
</span></span><span style="display:flex;"><span>       [<span style="color:#66d9ef">string[]</span>]$devices,
</span></span><span style="display:flex;"><span>       [parameter(<span style="color:#a6e22e">ParameterSetName</span>=<span style="color:#e6db74">&#34;names&#34;</span>,<span style="color:#a6e22e">Position</span>=<span style="color:#ae81ff">1</span>,<span style="color:#a6e22e">Mandatory</span>=$false,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ValueFromPipeline</span>=$false,<span style="color:#a6e22e">HelpMessage</span>=<span style="color:#e6db74">&#34;Look for certificate in local device.&#34;</span>)]
</span></span><span style="display:flex;"><span>       [<span style="color:#66d9ef">switch</span>]$local,
</span></span><span style="display:flex;"><span>       [parameter(<span style="color:#a6e22e">ParameterSetName</span>=<span style="color:#e6db74">&#34;files&#34;</span>,<span style="color:#a6e22e">Position</span>=<span style="color:#ae81ff">0</span>,<span style="color:#a6e22e">Mandatory</span>=$true,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ValueFromPipeline</span>=$true,<span style="color:#a6e22e">HelpMessage</span>=<span style="color:#e6db74">&#34;Enter PFX file name, Ex C:\folder\DEV123.pfx&#34;</span>)]
</span></span><span style="display:flex;"><span>       [<span style="color:#66d9ef">string[]</span>]$pfxfiles
</span></span><span style="display:flex;"><span>      )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> import-pfxbytes {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">param</span>($pfxBytes)
</span></span><span style="display:flex;"><span>   <span style="color:#75715e">## Import cert into a new object. No need to import it into a certificate device.</span>
</span></span><span style="display:flex;"><span>   $pfxPass = <span style="color:#e6db74">&#39;pFxP@$5w0rd&#39;</span>
</span></span><span style="display:flex;"><span>   $X509Cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
</span></span><span style="display:flex;"><span>   $X509Cert.Import([<span style="color:#66d9ef">byte[]</span>]$pfxBytes, $pfxPass,<span style="color:#e6db74">&#34;Exportable,PersistKeySet&#34;</span>)
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">return</span> $X509Cert
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> show-certinfo {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">param</span>($cert)
</span></span><span style="display:flex;"><span>   $cert | Select-Object -property Subject,Issuer,NotBefore,NotAfter,SerialNumber
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   $certChain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain
</span></span><span style="display:flex;"><span>   $result = $certChain.Build($cert)
</span></span><span style="display:flex;"><span>   $certChain.ChainPolicy.RevocationFlag = <span style="color:#e6db74">&#34;EntireChain&#34;</span>
</span></span><span style="display:flex;"><span>   $certChain.ChainPolicy.RevocationMode = <span style="color:#e6db74">&#34;Online&#34;</span>
</span></span><span style="display:flex;"><span>   Write-Host -Object <span style="color:#e6db74">&#34;Validating certficate chain...&#34;</span> -foreground black -background yellow
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">`r`n</span><span style="color:#e6db74">Valid</span><span style="color:#ae81ff">`t</span><span style="color:#e6db74">Certificate&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;-----</span><span style="color:#ae81ff">`t</span><span style="color:#e6db74">-----------&#34;</span>
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">foreach</span> ($element <span style="color:#66d9ef">in</span> $certChain.ChainElements) {
</span></span><span style="display:flex;"><span>       <span style="color:#e6db74">&#34;{0}</span><span style="color:#ae81ff">`t</span><span style="color:#e6db74">{1}&#34;</span> <span style="color:#f92672">-f</span> $element.Certificate.Verify(),$element.Certificate.Subject
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$Error.Clear()
</span></span><span style="display:flex;"><span>(<span style="color:#e6db74">&#34;=&#34;</span> * <span style="color:#ae81ff">50</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">switch</span> ($PsCmdlet.<span style="color:#a6e22e">ParameterSetName</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;names&#34;</span> {
</span></span><span style="display:flex;"><span>            $client = New-Object  -TypeName System.Net.WebClient
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">foreach</span> ($num <span style="color:#66d9ef">in</span> $devices) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> ($local) {
</span></span><span style="display:flex;"><span>                    $certs = Get-ChildItem -Recurse -Path Cert<span style="color:#960050;background-color:#1e0010">:</span> | Where-Object { $_.Subject <span style="color:#f92672">-like</span> <span style="color:#e6db74">&#34;CN=</span>$num<span style="color:#e6db74">&#34;</span> }
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span> ($certs.count <span style="color:#f92672">-eq</span> <span style="color:#ae81ff">0</span>) {
</span></span><span style="display:flex;"><span>                        <span style="color:#e6db74">&#34;No matching certificates found in the local device.&#34;</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">foreach</span> ($cert <span style="color:#66d9ef">in</span> $certs) {
</span></span><span style="display:flex;"><span>                        $certpath = $cert.pspath <span style="color:#f92672">-replace</span> <span style="color:#e6db74">&#39;Microsoft.PowerShell.Security\\Certificate::&#39;</span>,<span style="color:#e6db74">&#34;Cert:&#34;</span>
</span></span><span style="display:flex;"><span>                        Write-host -Object <span style="color:#e6db74">&#34;Reading </span>$certpath<span style="color:#e6db74">&#34;</span>  -foreground black -background yellow
</span></span><span style="display:flex;"><span>                        show-certinfo($cert)
</span></span><span style="display:flex;"><span>                        (<span style="color:#e6db74">&#34;=&#34;</span> * <span style="color:#ae81ff">50</span>)
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>                    $url = <span style="color:#e6db74">&#34;https://certbroker.contoso.com/pfxshare/</span>$($num)<span style="color:#e6db74">.pfx&#34;</span>
</span></span><span style="display:flex;"><span>                    Write-host -Object <span style="color:#e6db74">&#34;Downloading </span>$num<span style="color:#e6db74">.pfx&#34;</span> -foreground black -background yellow
</span></span><span style="display:flex;"><span>                    $pfxBytes = $client.DownloadData($url)
</span></span><span style="display:flex;"><span>                    $cert = import-pfxbytes($pfxBytes)
</span></span><span style="display:flex;"><span>                    show-certinfo($cert)
</span></span><span style="display:flex;"><span>                    (<span style="color:#e6db74">&#34;=&#34;</span> * <span style="color:#ae81ff">50</span>)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;files&#34;</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">foreach</span> ($file <span style="color:#66d9ef">in</span> $pfxfiles) {
</span></span><span style="display:flex;"><span>                Write-host -Object <span style="color:#e6db74">&#34;Reading </span>$file<span style="color:#e6db74">&#34;</span> -foreground black -background yellow
</span></span><span style="display:flex;"><span>                $pfxBytes = Get-Content -path $file -encoding Byte -ErrorAction:SilentlyContinue
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> ($error.count <span style="color:#f92672">-ne</span> <span style="color:#ae81ff">0</span>) {<span style="color:#66d9ef">throw</span> $error}
</span></span><span style="display:flex;"><span>                $cert = import-pfxbytes($pfxBytes)
</span></span><span style="display:flex;"><span>                show-certinfo($cert)
</span></span><span style="display:flex;"><span>                (<span style="color:#e6db74">&#34;=&#34;</span> * <span style="color:#ae81ff">50</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">catch</span> {
</span></span><span style="display:flex;"><span>    $_.Exception.Message
</span></span><span style="display:flex;"><span>    $_.InvocationInfo.PositionMessage
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>]]></content:encoded></item><item><title>Using PowerShell and EWS to monitor a mailbox</title><link>https://seanonit.org/blog/2014/10/using-powershell-and-ews-to-monitor-a-mailbox/</link><pubDate>Wed, 29 Oct 2014 00:00:00 -0500</pubDate><guid>https://seanonit.org/blog/2014/10/using-powershell-and-ews-to-monitor-a-mailbox/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>I support a suite of application services that implement our ITIL processes. One of the functions
allows users to create trouble tickets by sending a specially crafted email message to a specific
email address. The application has a service that polls that mailbox once a minute to retrieve those
messages and create new Incidents. Periodically, that email polling service stops working causing
messages to queue up in the mailbox. The process is still running and providing other functions but
it is no longer processing the inbound messages. We have monitoring on the system to watch that
service and alert us when it hangs or crashes. However, since the service is still running, we never
get alerted.</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>I support a suite of application services that implement our ITIL processes. One of the functions
allows users to create trouble tickets by sending a specially crafted email message to a specific
email address. The application has a service that polls that mailbox once a minute to retrieve those
messages and create new Incidents. Periodically, that email polling service stops working causing
messages to queue up in the mailbox. The process is still running and providing other functions but
it is no longer processing the inbound messages. We have monitoring on the system to watch that
service and alert us when it hangs or crashes. However, since the service is still running, we never
get alerted.</p>
<p>I needed another way to monitor for this problem. What if I could create a script that would check
the mailbox to see if there were any messages in the Inbox that had arrived more than a few minutes
ago? Since the service polled the mailbox every minute, any message more than five minutes old would
indicate that the polling service had stopped functioning. But how can you read the Inbox?</p>
<p>In the past, I have used VBScript to automate Outlook and manage email on my desktop. However, I
didn&rsquo;t want to install Outlook on the server. Installing Outlook incurs licensing costs and is way
more overhead than I really need. That also means that I need to manage patching for Outlook and
other Office components on a server which we don&rsquo;t normally do in our environment. Searching the
internet I found some scriptable POP3 and IMAP clients. Some were commercial and some open source.
But the cost of these options (in money and learning curve) was too high and the supportability was
questionable.</p>
<p>We use Microsoft Exchange for our email services. I know that PowerShell is now the preferred method
for most management tasks in Exchange. This led me to search for Exchange PowerShell options.</p>
<p>Enter Exchange Web Services (EWS) and its friendly.NET managed API. Installing and using the EWS
Managed API is simple. It can be installed on any Windows machine and does not require any other
Exchange components. Simply download the MSI package and install it.</p>
<ul>
<li>The EWS Managed API is now available as an open source project on <a href="https://github.com/officedev/ews-managed-api" target="_blank" rel="noopener noreferrer">GitHub<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
<li>See the <a href="https://learn.microsoft.com/dotnet/api/microsoft.exchange.webservices.data.emailmessage?view=exchange-ews-api" target="_blank" rel="noopener noreferrer">EWS Managed API reference<i class="fas fa-external-link-square-alt ms-1"></i></a></li>
</ul>
<p>I am running this script as a scheduled task on Window Server 2008 R2. The scheduled task runs once
per hour and is configured to run with domain credentials that have access to the target mailbox.
The script uses EWS to access the mailbox and check for stale messages. It also uses
Net.Mail.SmtpClient to send alert messages. I could have used EWS to send the alert message but
Net.Mail.SmtpClient is so much easier to use.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#75715e"># title=&#34;Scan-Mailbox.ps1&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">param</span>(
</span></span><span style="display:flex;"><span>    $mailboxName = <span style="color:#e6db74">&#39;new-tickets@contoso.com&#39;</span>,
</span></span><span style="display:flex;"><span>    $smtpServerName = <span style="color:#e6db74">&#39;smtp.contoso.com&#39;</span>,
</span></span><span style="display:flex;"><span>    $emailFrom = <span style="color:#e6db74">&#39;monitorservice@contoso.com&#39;</span>,
</span></span><span style="display:flex;"><span>    $emailTo = <span style="color:#e6db74">&#39;support@contoso.com&#39;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Load the EWS Managed API</span>
</span></span><span style="display:flex;"><span>Add-Type -Path <span style="color:#e6db74">&#39;C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>    $Exchange2007SP1 = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeVersion</span>]::Exchange2007_SP1
</span></span><span style="display:flex;"><span>    $Exchange2010    = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeVersion</span>]::Exchange2010
</span></span><span style="display:flex;"><span>    $Exchange2010SP1 = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeVersion</span>]::Exchange2010_SP1
</span></span><span style="display:flex;"><span>    $Exchange2010SP2 = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeVersion</span>]::Exchange2010_SP2
</span></span><span style="display:flex;"><span>    $Exchange2013    = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeVersion</span>]::Exchange2013
</span></span><span style="display:flex;"><span>    $Exchange2013SP1 = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeVersion</span>]::Exchange2013_SP1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># create EWS Service object for the target mailbox name</span>
</span></span><span style="display:flex;"><span>    $exchangeService = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ExchangeService</span>]::new($Exchange2010SP2)
</span></span><span style="display:flex;"><span>    $exchangeService.UseDefaultCredentials = $true
</span></span><span style="display:flex;"><span>    $exchangeService.AutodiscoverUrl($mailboxName)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># bind to the Inbox folder of the target mailbox</span>
</span></span><span style="display:flex;"><span>    $inboxFolderName = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.WellKnownFolderName</span>]::Inbox
</span></span><span style="display:flex;"><span>    $inboxFolder = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.Folder</span>]::Bind(
</span></span><span style="display:flex;"><span>        $exchangeService, $inboxFolderName)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Optional: reduce the query overhead by viewing the inbox 10 items at a time</span>
</span></span><span style="display:flex;"><span>    $itemView = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ItemView</span>]::new(<span style="color:#ae81ff">10</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># search the mailbox for messages older than 15 minutes</span>
</span></span><span style="display:flex;"><span>    $dateTimeItem = [<span style="color:#66d9ef">Microsoft.Exchange.WebServices.Data.ItemSchema</span>]::DateTimeReceived
</span></span><span style="display:flex;"><span>    $15MinutesAgo = (Get-Date).AddMinutes(<span style="color:#ae81ff">-15</span>)
</span></span><span style="display:flex;"><span>    $searchFilter = [Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo]::new(
</span></span><span style="display:flex;"><span>        $dateTimeItem, $15MinutesAgo)
</span></span><span style="display:flex;"><span>    $foundItems = $exchangeService.FindItems($inboxFolder.Id, $searchFilter, $itemView)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># report the results via email and Application event log</span>
</span></span><span style="display:flex;"><span>    $entryType = <span style="color:#e6db74">&#39;Information&#39;</span>
</span></span><span style="display:flex;"><span>    $date = Get-Date -Format <span style="color:#e6db74">&#39;MM/dd/yyyy hh:mm:ss&#39;</span>
</span></span><span style="display:flex;"><span>    $messageBody = <span style="color:#e6db74">&#34;Self-service mailbox scan completed at {0}.</span><span style="color:#ae81ff">`r`n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">-f</span> $date
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> ($foundItems.TotalCount <span style="color:#f92672">-ne</span> <span style="color:#ae81ff">0</span>) {
</span></span><span style="display:flex;"><span>        $entryType = <span style="color:#e6db74">&#39;Warning&#39;</span>
</span></span><span style="display:flex;"><span>        $subject = <span style="color:#e6db74">&#39;Self-service mailbox hung&#39;</span>
</span></span><span style="display:flex;"><span>        $messageBody = <span style="color:#e6db74">&#34;Inbox has {0} message(s) that are more than 15 minutes old.</span><span style="color:#ae81ff">`r`n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">-f</span> $foundItems.TotalCount
</span></span><span style="display:flex;"><span>        $messageBody += <span style="color:#e6db74">&#34;Inbox has {0} message(s) total.</span><span style="color:#ae81ff">`r`n`r`n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">-f</span> $inboxFolder.TotalCount
</span></span><span style="display:flex;"><span>        $messageBody += <span style="color:#e6db74">&#34;Please restart the Email Engine on SERVER01</span><span style="color:#ae81ff">`r`n</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        $messageBody += <span style="color:#e6db74">&#34;Self-service mailbox scan completed at {0}.</span><span style="color:#ae81ff">`r`n</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">-f</span> $date
</span></span><span style="display:flex;"><span>        $messageBody += <span style="color:#e6db74">&#34;Script run from </span>$env:computername<span style="color:#ae81ff">`r`n</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $smtpClient = [<span style="color:#66d9ef">Net.Mail.SmtpClient</span>]::new($smtpServerName)
</span></span><span style="display:flex;"><span>        $smtpClient.Send($emailFrom, $emailTo, $subject, $messageBody)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    $writeEventLogSplat = @{
</span></span><span style="display:flex;"><span>        LogName = <span style="color:#e6db74">&#39;Application&#39;</span>
</span></span><span style="display:flex;"><span>        Source = <span style="color:#e6db74">&#39;Application&#39;</span>
</span></span><span style="display:flex;"><span>        EventId = <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        Category = <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span>        EntryType = $entryType
</span></span><span style="display:flex;"><span>        Message = $messageBody
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    Write-EventLog @writeEventLogSplat
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> {
</span></span><span style="display:flex;"><span>    $entryType = <span style="color:#e6db74">&#39;Error&#39;</span>
</span></span><span style="display:flex;"><span>    $subject = <span style="color:#e6db74">&#39;Error in mailbox monitor script&#39;</span>
</span></span><span style="display:flex;"><span>    $messageBody = <span style="color:#e6db74">&#34;{0}</span><span style="color:#ae81ff">`r`n</span><span style="color:#e6db74">{1}&#34;</span> <span style="color:#f92672">-f</span> $_.Exception.Message, $_.InvocationInfo.PositionMessage
</span></span><span style="display:flex;"><span>    $writeEventLogSplat = @{
</span></span><span style="display:flex;"><span>        LogName = <span style="color:#e6db74">&#39;Application&#39;</span>
</span></span><span style="display:flex;"><span>        Source = <span style="color:#e6db74">&#39;Application&#39;</span>
</span></span><span style="display:flex;"><span>        EventId = <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        Category = <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span>        EntryType = $entryType
</span></span><span style="display:flex;"><span>        Message = $messageBody
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    Write-EventLog @writeEventLogSplat
</span></span><span style="display:flex;"><span>    $smtpClient = [<span style="color:#66d9ef">Net.Mail.SmtpClient</span>]::new($smtpServerName)
</span></span><span style="display:flex;"><span>    $smtpClient.Send($emailFrom, $emailTo, $subject, $messageBody)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>]]></content:encoded></item><item><title>Fun with Regular Expressions</title><link>https://seanonit.org/blog/2014/10/fun-with-regular-expressions/</link><pubDate>Tue, 21 Oct 2014 00:00:00 -0500</pubDate><guid>https://seanonit.org/blog/2014/10/fun-with-regular-expressions/</guid><description><![CDATA[<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>A while back a friend of mine mentioned that he could not find a regular expression that was capable
of parsing Windows Performance counter strings. He said that it couldn&rsquo;t be done with regex alone
and he had written a lot of code to manually parse the strings. That sounded like a challenge to me.
I had recently been working on a project where I needed regular expressions to find and clean up
text that I was extracting from a large database. I had spent a lot of time learning what I could
about regex to make the job easier. Along the way, I found a great tool called <a href="http://www.ultrapico.com/Expresso.htm" title="Expresso" target="_blank" rel="noopener noreferrer">Expresso<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>]]></description><content:encoded><![CDATA[
<blockquote class="alert alert-info" role="alert">
    <p class="alert-heading fw-bold">
      <i class="fas fa-info-circle me-2"></i>Note
    </p>
    <p>This article was originally posted to my old <a href="https://seanonit.wordpress.com/" target="_blank" rel="noopener noreferrer">WordPress blog<i class="fas fa-external-link-square-alt ms-1"></i></a>. The content is still relevant
but some details may have changed.</p>
</blockquote>
<p>A while back a friend of mine mentioned that he could not find a regular expression that was capable
of parsing Windows Performance counter strings. He said that it couldn&rsquo;t be done with regex alone
and he had written a lot of code to manually parse the strings. That sounded like a challenge to me.
I had recently been working on a project where I needed regular expressions to find and clean up
text that I was extracting from a large database. I had spent a lot of time learning what I could
about regex to make the job easier. Along the way, I found a great tool called <a href="http://www.ultrapico.com/Expresso.htm" title="Expresso" target="_blank" rel="noopener noreferrer">Expresso<i class="fas fa-external-link-square-alt ms-1"></i></a>.</p>
<p><picture><img class="img-fluid " alt="Expresso showing the parsed results of this regex" src="/blog/2014/10/fun-with-regular-expressions/expresso.png" loading="lazy" width="837" height="780" />
</picture>

</p>
<p>Expresso is a power tool for developing and testing regular expressions. In just a few minutes I was
able to create a regex that fit the bill. Since he was writing code in PowerShell to process these
performance counters I sent him this proof of concept.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>$ctrs = (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;\\IDCWEB1\Processor(_Total)\% Processor Time&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;\Paging File(\??\C:\pagefile.sys)\% Usage Peak&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;\MSSQL$SQLServer:Memory Manager\Total Server Memory (KB)&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;\\BLACKVISE\Paging File(\??\C:\pagefile.sys)\% Usage Peak&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;\Category(Instance(x))\Counter (x)&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;\SQLServer:Latches\Latch Waits/sec (ms)&#39;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$pattern = <span style="color:#e6db74">&#39;(?&lt;srv&gt;\\\\[^\\]*)?\\(?&lt;obj&gt;[^\(^\)]*)(\((?&lt;inst&gt;.*(\(.*\))?)\))?\\(?&lt;ctr&gt;.*\s?(\(.*\))?)&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">foreach</span> ($ctr <span style="color:#66d9ef">in</span> $ctrs) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> ($ctr <span style="color:#f92672">-match</span> $pattern) {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Server = &#34;</span> + $matches[<span style="color:#e6db74">&#34;srv&#34;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Object = &#34;</span> + $matches[<span style="color:#e6db74">&#34;obj&#34;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Instance = &#34;</span> + $matches[<span style="color:#e6db74">&#34;inst&#34;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Counter = &#34;</span> + $matches[<span style="color:#e6db74">&#34;ctr&#34;</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here is the output :</p>
<pre tabindex="0"><code>Server = \\IDCWEB1
Object = Processor
Instance = _Total
Counter = % Processor Time

Server =
Object = Paging File
Instance = \??\C:\pagefile.sys
Counter = % Usage Peak

Server =
Object = MSSQL$SQLServer:Memory Manager
Instance =
Counter = Total Server Memory (KB)

Server = \\BLACKVISE
Object = Paging File
Instance = \??\C:\pagefile.sys
Counter = % Usage Peak

Server =
Object = Category
Instance = Instance(x)
Counter = Counter (x)

Server =
Object = SQLServer:Latches
Instance =
Counter = Latch Waits/sec (ms)
</code></pre><p>By the way, the PowerShell script he was writing was part of <a href="https://github.com/clinthuffman/PAL" target="_blank" rel="noopener noreferrer">PAL<i class="fas fa-external-link-square-alt ms-1"></i></a>. Check out <a href="https://github.com/clinthuffman" target="_blank" rel="noopener noreferrer">Clint&rsquo;s<i class="fas fa-external-link-square-alt ms-1"></i></a>
incredible performance analysis tool.</p>
]]></content:encoded></item></channel></rss>