Jekyll2021-07-02T15:23:52+00:00https://www.foxdeploy.com/feed.xmlFoxDeploy.comSystems Management tails from the foxholeGot messy Ifs? Guard Clauses to the Rescue!2021-07-02T00:00:00+00:002021-07-02T00:00:00+00:00https://www.foxdeploy.com/blog/got-messy-ifs-guard-clauses-to-the-rescue<p>Revisiting PowerShell after mostly writing nothing but c# for years, I’m finding lots of useful programming practices can make my code easier to read. In this post, we’ll talk about guard clauses and how they can make your code easier to read!</p>
<p><img src="\assets\images\2021\Guard-Clauses.png" alt="Header for this post, reads 'Protecting your code with guard clauses'" /></p>
<p>This is another one of those posts inspired by a Stack Overflow question (these things just write themselves!). <a href="https://stackoverflow.com/questions/68210680/powershell-multiple-parameters-conditions/68211807#68211807">Here’s the post that inspired it.</a></p>
<p><em>Post Outline</em></p>
<ul>
<li>What are guard clauses?</li>
</ul>
<p>In programming, imagine you have some function like this which could presumably bail early if it’s not going to be able to do the work you need done.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void SomeMethod<T>(string var1, IEnumerable<T> items, int count)
{
if (string.IsNullOrEmpty(var1))
{
throw new ArgumentNullException("var1");
}
if (items == null)
{
throw new ArgumentNullException("items");
}
if (count < 1)
{
throw new ArgumentOutOfRangeException("count");
}
... etc ....
}
</code></pre></div></div>
<p>There a lot of reasons we could bail, and the code looks pretty messy because of it.</p>
<p>We can introduce a guard clause here, and the technique works in PowerShell or C#, to contain all the messy “stuff that makes my function die” logic.</p>
<p>We can also use them to make our <code class="language-plaintext highlighter-rouge">ifs</code> easier to read too!</p>
<h1 id="stuff-that-makes-you-go-die">Stuff that makes you go die</h1>
<p>Some places write <code class="language-plaintext highlighter-rouge">Ensure</code> methods to handle bailing on certain conditions, like this.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public static class Ensure{
public static void IsNotNull(object val, string arg)
{
if (value == null)
throw new ArgumentNullException(argument);
}
}
</code></pre></div></div>
<p>You’d then modify the code to consume it like so:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void SomeMethod<T>(string var1, IEnumerable<T> items, int count)
{
Ensure.IsNotNull(var1);
Ensure.IsNotNull(items);
</code></pre></div></div>
<p>You can even go farther and just stuff all of your ‘sadpath’ logic into one guard.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static class Ensure{
public static void CanProcess(object val, string arg)
{
if (val == null)
throw new ArgumentNullException(argument);
if (val.GetType().IsArray && val.Count < 1 )
throw new ArgumentOutOfRangeException("count");
}
}
</code></pre></div></div>
<p>Then you just bail out early and easily, shortening your code.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void SomeMethod<T>(string var1, IEnumerable<T> items, int count)
{
new [] {var1, items, count}.ForEach(x=>Ensure.CanProcess(x));
... etc ....
}
</code></pre></div></div>
<blockquote>
<p>But Stephen, can’t we just use parameter validation for these?</p>
</blockquote>
<p>Sure, you can and should use parameter validation to ensure your function can work, but there are loads of common scenarios when you will have special handling for special combinations, and guard clauses are an awesome tool for simplifying that logic.</p>
<p>Which brings us to…</p>
<h1 id="simplifying-my-ifs">Simplifying my Ifs</h1>
<p><img src="\assets\images\2021\guardCluase_srGrfo.png" alt="The 'protector meme' with complex if statements being blocked and a nice 'ShouldProcess()' guard clause coming out" /></p>
<p>In PowerShell specifically, we already have parameter validation, so most people can and should use that to clean up and help our function not have to worry about the sad paths out there.</p>
<p>For PowerShell, I love the use case of using these little guard functions to instead return <code class="language-plaintext highlighter-rouge">true</code> or <code class="language-plaintext highlighter-rouge">false</code> and then we just plop them directly into an <code class="language-plaintext highlighter-rouge">if</code> statement.</p>
<p>These are great because…</p>
<h1 id="guard-clauses-help-us-to-express-ourselves-through-code">Guard clauses help us to express ourselves through code</h1>
<p>Imagine a function like this, which wraps a call to <code class="language-plaintext highlighter-rouge">Write-Host</code> (thank you to <a href="https://twitter.com/IISResetMe">@IISResetMe</a> for the sample).</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#updating Mathias to add calling the guard clauses</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">Write-CustomHost</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="kr">param</span><span class="p">(</span><span class="w">
</span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)]</span><span class="w">
</span><span class="nv">$Object</span><span class="p">,</span><span class="w">
</span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">Mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="n">ValidateSet</span><span class="p">(</span><span class="s1">'Red'</span><span class="p">,</span><span class="s1">'Green'</span><span class="p">)]</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$ForegroundColor</span><span class="w">
</span><span class="p">)</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="err">@</span><span class="nx">PSBoundParameters</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Now, imagine we needed some special handling when we got in a Process object, or, for simplicities sake, an <code class="language-plaintext highlighter-rouge">[int]</code> object.</p>
<p>We <em>could</em> write this kind of an if clause.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="nv">$object</span><span class="w"> </span><span class="o">-is</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">])</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="nv">$foreGroundColor</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'red'</span><span class="p">))</span><span class="w">
</span></code></pre></div></div>
<p>Which maybe isn’t too hard to read. But what about when we need even more complex behavior, like handling combinations of params coming in?</p>
<p>Cases like these, where our <code class="language-plaintext highlighter-rouge">ifLogic</code> goes into a simple function to give a <code class="language-plaintext highlighter-rouge">[bool]</code> response save the day.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#guardClause setup</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">isRed</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="kr">param</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$ForegroundColor</span><span class="p">)</span><span class="w">
</span><span class="nv">$ForegroundColor</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'Red'</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">isInt</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="kr">param</span><span class="p">(</span><span class="nv">$object</span><span class="p">)</span><span class="w">
</span><span class="nv">$object</span><span class="w"> </span><span class="o">-is</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>We don’t need much logic at all, just a simple PowerShell comparison statement. Now, we go back to our custom host and….</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#updating Mathias to add calling the guard clauses
function Write-CustomHost {
param(
#...
)
if (isRed $ForegroundColor -and isInt $Object){
return "this is a red Int, so lets do special handling here"
}
Write-Host @PSBoundParameters
}
Write-CustomHost -Object 1 -ForegroundColor Red
</code></pre></div></div>
<p><img src="\assets\images\2021\guardCluase_example.png" alt="The 'protector meme' with complex if statements being blocked and a nice 'ShouldProcess()' guard clause coming out" /></p>
<h2 id="do-i-really-need-to-write-tests-for-these">Do I really NEED to write tests for these?</h2>
<p><strong>Yes of course you do</strong>. If code is worth writing, it’s worth testing. These are especially easy to test, but proper testing should include a test for both possible conditions for each clause.</p>
<p>At a minimum, there should be:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Describe</span><span class="w"> </span><span class="nx">GuardCluases</span><span class="p">{</span><span class="w">
</span><span class="n">It</span><span class="w"> </span><span class="s2">"IsInt should return true when an int"</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="n">IsInt</span><span class="w"> </span><span class="nx">4</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">should</span><span class="w"> </span><span class="nt">-be</span><span class="w"> </span><span class="bp">$true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">It</span><span class="w"> </span><span class="s2">"IsInt should return false when not an int"</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="n">IsInt</span><span class="w"> </span><span class="s2">"ok"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">should</span><span class="w"> </span><span class="nt">-Be</span><span class="w"> </span><span class="bp">$false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>Stephen OwenRevisiting PowerShell after mostly writing nothing but c# for years, I'm finding lots of useful programming practices can make my code easier to read. In this post, we'll talk about guard clauses and how they can make your code easier to read!Jekyll Migration From WordPress - What about the stats?!2021-04-13T00:00:00+00:002021-04-13T00:00:00+00:00https://www.foxdeploy.com/blog/jekyll-migration-from-wordpress-what-about-the-stats<p>Recently I migrated my blog off of WordPress hosting and over to static sites generated from Markdown files, hosted on GitHub Pages.</p>
<p>In migrating from Wordpress.com hosting, I noticed that there wasn’t any great one step solution for the ‘WordPress Stats’ info I got from WordPress’ JetPack Service. In this post, I’ll show you exactly how I recreated both the utility AND more importantly the Vanity I got from WordPress Stats (the later to satisfy my ego!)</p>
<p><img src="\assets\images\2021\trackingStates.webp" alt="Header for this post, reads 'How To Make GitHub Button'" /></p>
<p><em>Post Outline</em></p>
<ul>
<li>What were WordPress Stats?</li>
<li>Finding a way to satisfy my ego</li>
<li>Automatically adding it below posts in Jekyll</li>
<li>How does this compare to Google Analytics?</li>
</ul>
<h2 id="what-are-wordpress-stats">What are WordPress Stats</h2>
<p>If you’re a long time reader of blogs but don’t blog on your own, you might never have paid much attention to little widgets like these on the sites you visit.</p>
<p><img src="/assets/images/2021/jekyll_statCounter.png" alt="shows a common website stat counter, which reads 'Hits to this blog', and shows the number, 2.7 million" /></p>
<p>But believe me, the authors of those sites <strong>definitely</strong> know what I’m talking about.</p>
<p>What does it do? Well, it might be a little bit old school but it just keeps track of the page loads of a site. If someone comes and visits an article, then clicks to see some more in the series and ends up looking at four other articles, it would tick up five more times.</p>
<p>If you host your blog on WordPress, one of the nifty features you can enable is a Plugin called JetPack which gives you a number of cool features, one of which is the stat tracker I showed above.</p>
<p>Unfortunately if you migrate away from WordPress…you can’t bring it with you.</p>
<h2 id="why-do-you-even-need-this-though">>Why do you even need this though?</h2>
<p><img src="/assets/images/2021/jekyll_statMarge.webp" alt="depicts Marge Simpson from the Simpsons holding a potatoe saying 'I just think they're neat" /></p>
<h2 id="can-we-just-hack-this-into-place">Can we just hack this into place?</h2>
<p>Or can we?? I originally looked at monitoring to see if the request to the WordPress.com hosted site had an obvious method to…extract the call to the API used to track hits to the site.</p>
<p>Unfortunately, stats seem to be tracked as part of the page load request, and not a separate API call.</p>
<p>Furthermore, while their API does allow you to query the current traffic stats and milestones, they do not allow you to increment the hits with a simple call that I could find.</p>
<p>This didn’t stop me though, I needed a way to see my pretty numbers counting up!</p>
<h1 id="finding-a-way-to-satisfy-my-ego">Finding a way to satisfy my ego</h1>
<p>All I <em>really</em> needed was an API that could increment simply. I thought of writing my own dotnet core app, as I’ve done a bunch of times, like when I wrote a Game of Throne Deathpool site with logons and passwords, or when I wrote a simple app to check the UV levels of a given day (needed because of my ghastly palor, due to being a redhead).</p>
<p>But then…I got lazy.</p>
<p><img src="/assets/images/2021/lazyCat.webp" alt="depicts an adorable small black kitten asleep on a laptop keyboard" /></p>
<p><em>My normal level of energy on a given day</em></p>
<p>So I hunted for an easy peasey API, and found a great one in <a href="https://api.countap.xyz](https://api.countapi.xyz/)">countapi.xyz</a></p>
<p>All you do with this API is register your new counter with an optional reset / control code and then trigger a Get request to increment and retrieve the new value!</p>
<p>So to create a new tracker for a site called <code class="language-plaintext highlighter-rouge">mysite.com</code> and then set its starting value at 42, we’d run:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">GET</span> <span class="nx">https</span><span class="p">:</span><span class="c1">//api.countapi.xyz/create?namespace=mysite.com&value=42</span>
<span class="err">⇒</span> <span class="mi">200</span> <span class="p">{</span><span class="dl">"</span><span class="s2">namespace</span><span class="dl">"</span><span class="p">:</span><span class="dl">"</span><span class="s2">mysite.com</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">key</span><span class="dl">"</span><span class="p">:</span><span class="dl">"</span><span class="s2">33606dbe-4800-4228-b042-5c0fb8ec8f08</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="p">:</span><span class="mi">42</span><span class="p">}</span>
</code></pre></div></div>
<p>And then, after that, we only need to run a new get request to see that we’re up and running.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Invoke-RestMethod</span><span class="w"> </span><span class="s2">"https://api.countapi.xyz/hit/foxdeployhits"</span><span class="w">
</span><span class="err">></span><span class="mi">2075540</span><span class="w">
</span></code></pre></div></div>
<h2 id="getting-it-working-in-javascript">Getting it working in JavaScript</h2>
<p>Now I have a mechanism to track my hits, I just need to actually trigger this when the page is loaded. This needs to happen client side (which sadly means I lose tracking and counts if the user has JavaScript disabled or very strict settings, <em>c`est la vie</em>).</p>
<h2 id="automatically-adding-it-to-posts-in-jekyll">Automatically adding it to posts in Jekyll</h2>
<p>This was really easy. My blog theme, Bulma-Clean-Theme already had a look and feel I liked from my site on WordPress <a href="https://www.foxdeploy.wordpress.com">foxdeploy.wordpress.com</a>, so with a minimal amount of configuration, I had something that felt familiar.</p>
<p>It even had a great sidebar function, provided via a file called <code class="language-plaintext highlighter-rouge">Latest-Posts.html</code>. Here’s mine if you want to see what it looks like:</p>
<p><a href="https://github.com/1RedOne/1redone.github.io/blob/master/_includes/latest-posts.html">latest-posts</a></p>
<p><img src="/assets/images/2021/tailsBlush.gif" alt="depicts Tails from Sonic the Hedge, blushing" /></p>
<p><strong>when someone looks at my source code</strong></p>
<p>Adding a new node here to mimic the ‘Page Stats’ feature was very easy, just adding a new div with the right class.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"card"</span> <span class="na">style=</span><span class="s">"padding-top:20px;"</span><span class="nt">></span>
<span class="nt"><header</span> <span class="na">class=</span><span class="s">"card-header"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"card-header-title"</span><span class="nt">></span>Blog Stats<span class="nt"></a></span>
<span class="nt"></header></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"card-content"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"content"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">'blogHits'</span><span class="nt">></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
</code></pre></div></div>
<blockquote>
<p><strong>Note:</strong> Pay special attention to the currently empty div called <em>blogHits</em> above, for now it will be empty but we’ll use this as an anchor to inject a value in just a moment!</p>
</blockquote>
<p>Now to reload and see how it shows up…</p>
<p><img src="/assets/images/2021/jekyll_statCounter_empty.png" alt="shows a new node added to a menu with the text content of 'Blog Stats' but no value" /></p>
<p>I can trigger a http request when this element is loaded by adding a bit of JS, like this:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o"><</span><span class="nx">Script</span><span class="o">></span>
<span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">https://api.countapi.xyz/hit/foxdeployhits</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">responseType</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">json</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">onload</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">blogHits</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerText</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">value</span> <span class="o">+</span> <span class="dl">'</span><span class="s1"> hits</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">total hits </span><span class="dl">"</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
<span class="o"><</span><span class="sr">/Script</span><span class="err">>
</span></code></pre></div></div>
<p>With this code, we prepare a GET request over to countapi.xyz, and then onload (when we have a response), we scan the document object model (the DOM, a code based interpretation of the current webpage, which feels <strong>very familiar</strong> coming from PowerShell) to find the empty div we setup earlier.</p>
<p>This works BUT….the number is uggo.</p>
<p><img src="/assets/images/2021/jekyll_statCounter_valueweird.png" alt="shows a new node added to a menu with the text content of 'Blog Stats' but no value" /></p>
<p>Fortunately JavaScript has approximate 857,3028,237 different built in formatting tools, and I can make use of the convenient <code class="language-plaintext highlighter-rouge">.toLocaleString('en')</code> utility to transpose strings into different formats. Because I’m Anglo-centric, I have chose the <strong>right way</strong> to do it, using comma separators. Sorry if you like other format types!</p>
<p>With this in place…</p>
<p><img src="/assets/images/2021/jekyll_statCounter_complete.png" alt="shows a new node added to a menu with the text content of 'Blog Stats' but no value" /></p>
<h2 id="how-does-this-compare-to-google-analytics">How does this compare to Google Analytics?</h2>
<p>Frankly, Google Analytics gives me INCREDIBLE amounts of data and is dramatically far and away much, much better than what I had before with WordPress stats. It even has a load of tools to show me ways I can improve the site, like Accessibility and page loads.</p>
<p>I did a lot of work transcribing Accessibility and alt tags onto images, and now have a much better layout for keyboard navigation, and now no longer have screen shots of code, and the site loads much faster.</p>
<p>All of these issues were surfaced to me by Google Analytics, which is frankly the bomb, and I will probably write a blog post about it later on!</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>Did you like this post? Are you interested in what it took to migrate my site from WordPress over to GitHub pages? You’re in luck, I have some more posts planned for that topic! Leave me a message below and lemme know!</p>
<p>🐦 - Shout out to Chris for the excellent <a href="https://github.com/chrisrhymes/bulma-clean-theme">Bulma Clean Theme</a></p>
<p>🐦 - Shout out to this <a href="https://tutorial.eyehunts.com/js/javascript-number-format-comma-html-format-number-thousands-separator/">blog post by Rohit from Eyehunts</a> for the tip on how to format numbers in a way that makes me happy.</p>Stephen OwenIn migrating from Wordpress.com hosting, I noticed that there wasn't any great one solution for the 'WordPress Stats' info I got from WordPress' JetPack Service. In this post, I'll show you exactly how I recreated both the utility AND more importantly the Vanity I got from WordPress Stats, seeing big numbers pop up like I'm back in WoW...Introducing DogDeploy2021-04-01T00:00:00+00:002021-04-01T00:00:00+00:00https://www.foxdeploy.com/blog/introducing-dogdeploy<h2 id="introducing-dogdeploy">Introducing DogDeploy</h2>
<p>After years of blogging about PowerShell, ConfigMgr and Automation, I decided to give it all up and now focus the blog solely on my new love, Dogs…</p>
<p>Or not! But if you’re like my friend Ryan Engstrom on Twitter, and wanted me to compile a list of my funny ‘#Important Bernie Size Update’ posts…</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Please put together a thread of all your 'dog size update' photos.</p>— Ryan Engstrom (@ryandengstrom) <a href="https://twitter.com/ryandengstrom/status/1366128830419783682?ref_src=twsrc%5Etfw">February 28, 2021</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Then check out the April Fools Page, over here, by clicking the link below!</p>
<p><a href="/dogDeploy"><img src="..\assets\images\dogDeployHero.webp" alt="Header for this post, which is the normal FoxDeploy logo but with a shaggy dog in place of foxes'" /></a></p>
<p>Or click the little Bernie button floating up above!</p>Stephen OwenAfter years of blogging about PowerShell, ConfigMgr and Automation, I decided to give it all up and now focus the blog solely on my new love, Dogs.GitHub Report Issue Button2021-03-15T00:00:00+00:002021-03-15T00:00:00+00:00https://www.foxdeploy.com/blog/github-report-issue-button<h2 id="github-report-issue-button">GitHub Report Issue Button</h2>
<p>I have long admired the Report Issue button on Microsoft Docs and sought to recreate it. Then I loved it so much, I added it to the end of every post! Here’s how, and you can just copy and paste it and then take the rest of the day off!</p>
<p><img src="..\assets\images\2021\title_GithubButton.webp" alt="Header for this post, reads 'How To Make GitHub Button'" /></p>
<p><em>Post Outline</em></p>
<ul>
<li>Example of What I’m talking about</li>
<li>Why is this useful?</li>
<li>OK, I’m sold, let’s make one!</li>
<li>How to make it into a snippet</li>
<li>Automatically adding it below posts in Jekyll</li>
</ul>
<h2 id="what-are-you-smoking-stephen">What are you smoking, Stephen?</h2>
<p>This is a reasonable question to ask anyone, and especially me.</p>
<p>Way back in the day when I was writing a Wiki or docs for work, I knew that if I had a bug or error in my documentation, I could count on my good ol’ buddy <a href="https://twitter.com/waingrositblog">Wayne from @waingrositblog</a> to spin in his chair and shout</p>
<blockquote>
<p>Yo, this shit is whack, son</p>
</blockquote>
<p>before he went back to looking at collectible Transformer figurines and then you knew you had to make some changes to your docs.</p>
<p>But not everyone had a Wayne of their own. So eventually everyone else started rolling comments on their blogs with Disqus or something similar. However they got flooded by Spam and noise and it was hard to stay on top of them.</p>
<p>So now we’re left with needing a way for people to report issues in your posts without using comments which are hard to stay on top of without a lot of effort.</p>
<p>Enter the Report Issue Button, which can be found in a lot of Microsoft Docs, <a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/api-custom-events-metrics">like this page on adding Application Insights to your app</a>.</p>
<p><img src="..\assets\images\2021\issuebuttonInAction.png" alt="shows a Microsoft docs page with a Feedback bar, with two buttons. One button reads 'This Product', and the other reads 'This Page'" /></p>
<h2 id="why-is-this-useful">Why is this useful?</h2>
<p>Clicking this button takes the user to the ‘Report an issue’ page but also populated the body of the issue with a lot of useful info.</p>
<p><img src="..\assets\images\2021\issueButtonBody.png" alt="Shows the Github repository for Azure Docs, opening a new Issue, with a body for the issue popualted with lots of fields listed below the image" /></p>
<p>This implementation is great, because it automagically includes a lot of info about the post including which specific URL they viewed and also a link to the source file, so the dev or whoever isn’t left wondering what the heck the person is talking about.</p>
<p>I loved this and knew that I with me hosting my new blog on GitHub pages, I wanted to rely on GitHub Issues as well to keep track of needed changes and feedback.</p>
<h2 id="ok-im-sold-lets-make-one">OK, I’m sold, let’s make one!</h2>
<p>The first thing I did was right-click and inspect element to get a clue as to how this button works. This revealed that it looks like you can do a lot with simple URL encoded values to set things like the Issue Title and prefill Issue Comments.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://github.com/MicrosoftDocs/azure-docs/ #githubRepo
issues/new? #Issues / new
title=& #/No title but it looks like thats an option body=...
</code></pre></div></div>
<p>And with a little HTML decode on the body, we can see that it translates into exactly what you see in the body when click ‘Report Issue’</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Enter feedback here]
---
#### Document Details
⚠ *Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.*
* ID: 401d2997-0fbd-7966-8996-edbaab1819ff
* Version Independent ID: c3ee3318-4c69-8ded-a8d5-5059d699351b
* Content: [Application Insights API for custom events and metrics - Azure Monitor](https://docs.microsoft.com/en-us/azure/azure-monitor/app/api-custom-events-metrics)
* Content Source: [articles/azure-monitor/app/api-custom-events-metrics.md](https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/azure-monitor/app/api-custom-events-metrics.md)
* Service: **azure-monitor**
* Sub-service: **application-insights**
* GitHub Login: @lgayhardt
* Microsoft Alias: **lagayhar**
</code></pre></div></div>
<p>Now, I liked some of those fields but I didn’t need all of them, so I trimmed down to just these fields.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Enter feedback here]
---
Document Details
- Content [GitHub Report Issue Button](https://1redone.github.io//blog/github-report-issue-button.html)
- Content Source [_posts/2021-03-15-github-report-issue-button.md](https://github.com/1RedOne/1RedOne.Github.io/blob/master/_posts/2021-03-15-github-report-issue-button.md)
</code></pre></div></div>
<p>The content and Content Source items are MarkDown Links, using the Jekyll Liquid Tags of <code class="language-plaintext highlighter-rouge">page.title</code> and <code class="language-plaintext highlighter-rouge">page.path</code> to autofill those values. This will help me see which URL someone was viewing and also what file that correlates to in the GitHub repo as well.</p>
<p>Next, to test the results I just pasted this into some random blog post and fired it up in Jekyll to see the result.</p>
<p><img src="..\assets\images\2021\githubIssueResult.png" alt="" /></p>
<p>It works! It has both a working Content link, to show me where they clicked from, and the file path so I can edit the post too.</p>
<p>Now I just need to HTML Encode this body and make it into a clickable button.</p>
<h2 id="url-encoding-and-you">URL Encoding and you</h2>
<p>Do enough web dev work and eventually you won’t see <code class="language-plaintext highlighter-rouge">%20</code> anymore, but automatically interpet it as a space. That’s the power of URL encoding. And of the human brain!</p>
<p>The easiest way to do this in a human readable form is to make a variable in JavaScript with the plain text, and then call <code class="language-plaintext highlighter-rouge">encodeURI</code> and let JS do it all for me.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"alert alert-info"</span> <span class="na">role=</span><span class="s">"alert"</span><span class="nt">></span>
<span class="nt"><i></span>See an Issue with this page? Report it on Github Automatically!<span class="nt"></i><br></span>
<span class="nt"><button</span> <span class="na">class=</span><span class="s">"button"</span> <span class="na">id=</span><span class="s">'GitHubButton'</span><span class="nt">></span>
<span class="nt"><i</span> <span class="na">class=</span><span class="s">"fab fa-github fa-lg fa-pull-left"</span><span class="nt">></i></span> Report Issue
<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"><script></span>
<span class="kd">function</span> <span class="nx">MakeLink</span><span class="p">(){</span>
<span class="kd">var</span> <span class="nx">urlToEncode</span> <span class="o">=</span> <span class="s2">`https://github.com/1RedOne/1RedOne.Github.io/issues/new?title=BlogPostIssue&amp;body=
[Enter feedback here]
---
Document Details
- Content [GitHub Report Issue Button](https://1redone.github.io//blog/github-report-issue-button.html)
- Content Source [_posts/2021-03-15-github-report-issue-button.md](https://github.com/1RedOne/1RedOne.Github.io/blob/master/_posts/2021-03-15-github-report-issue-button.md)
`</span><span class="p">;</span>
<span class="k">return</span> <span class="nb">encodeURI</span><span class="p">(</span><span class="nx">urlToEncode</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">gitHubURL</span> <span class="o">=</span> <span class="nx">MakeLink</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">setting button for </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">gitHubURL</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">item</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">GitHubButton</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">item</span><span class="p">.</span><span class="nx">onclick</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span><span class="nb">window</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="nx">gitHubURL</span><span class="p">);}</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>Then I added this manually to a page, refreshed Jekyll…and what did I see?</p>
<p><img src="..\assets\images\2021\GitHubReportButton.png" alt="Shows a button with the Github icon which reads 'Report Issue'" /></p>
<p>It was that easy! Now, I could go paste this into every single post I ever wrote, but I am a bit lazier than that…</p>
<h2 id="how-to-make-it-into-a-snippet">How to make it into a snippet</h2>
<p>Much like basically any other web framework (what an astute statement, Stephen) Jekyll supports saving common UI elements as a snippet which can be included and embedded wherever you like, making the perfect composited UI.</p>
<p>All you do is make a new <code class="language-plaintext highlighter-rouge">html</code> file and drop it into your <code class="language-plaintext highlighter-rouge">./includes</code> folder and you’ll be good to go.</p>
<p>I took this chance to spruce up the button into a full modal element.</p>
<script src="https://gist.github.com/ad1722d3fa40f4ad19056e885fd259a1.js"> </script>
<p><img src="..\assets\images\2021\completedModal.png" alt="Shows a working Report Issue Github button below a blog post." /></p>
<h2 id="automatically-adding-it-below-posts-in-jekyll">Automatically adding it below posts in Jekyll</h2>
<p>Adding this snippet automatically is super easy. With it saved in the <code class="language-plaintext highlighter-rouge">./includes</code> folder, we can then reference it within any page manually by using this syntax.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>`{`% include gitHubLink.html `%`}
</code></pre></div></div>
<blockquote>
<p>Remove the backticks!</p>
</blockquote>
<p>So now our final step is to add this to our default <code class="language-plaintext highlighter-rouge">post.html</code> layout, which will be found in the layouts folder.</p>
<p>Merge the new <code class="language-plaintext highlighter-rouge">include</code> statement here after post content.</p>
<script src="https://gist.github.com/c923f896d01ae1470f7b84a6ad24d99b.js"> </script>
<p>And then after a Jekyll Rebuild, it should appear beneath your posts!</p>Stephen OwenI have long admired the Report Issue button on Microsoft Docs and sought to recreate it. Then I loved it so much, I added it to the end of every post! Here's how, and you can just copy and paste it and then take the rest of the day off!PowerShell Easy Caching2021-03-01T00:00:00+00:002021-03-01T00:00:00+00:00https://www.foxdeploy.com/blog/powershell-easy-caching<h2 id="powershell-easy-caching">PowerShell Easy Caching</h2>
<p><img src="..\assets\images\2021\Caching-For-Speed.webp" alt="" /></p>
<p>The other day I <a href="https://stackoverflow.com/questions/65959734/i-want-to-implement-a-cache-storage-on-my-powershell-for-quicker-runtime-on-next/65960196#65960196">answered a question on StackOverflow</a> about how to cache the results of slow running operations easily in PowerShell.</p>
<p>In answering it, I was reminded that this problem occurs all the time in automation, like when you:</p>
<ul>
<li>need to work with a big dataset</li>
<li>have a very slow external dependency</li>
<li>your queries impact the environment</li>
<li>have a rapid automation task which can still function with semi-stale data</li>
</ul>
<p>As always, we will approach this with Progressive Automation, step-by-step adding complexity and features till we get something we’re really proud about. So first we’ll look at Caching calls just for this instance of PowerShell. Then we’ll build in complexity and add a more persistent cache.</p>
<p>One new thing: I’m going to try to start sprinkling in some more deliberate career / workplace advice throughout my posts, hope you like it. If you hate it, feel free to contact me for a refund.</p>
<p><em>Outline</em></p>
<ul>
<li>Question Background</li>
<li>Identify key places to add caching</li>
<li>Add a soft-cache (memory caching)</li>
<li>Generalize to be used anywhere</li>
<li>A Great Use for PowerShell Classes is found!</li>
<li>Upgrade to disk backed cache</li>
</ul>
<h2 id="question-background">Question Background</h2>
<p><a href="https://stackoverflow.com/questions/65959734/i-want-to-implement-a-cache-storage-on-my-powershell-for-quicker-runtime-on-next/65960196#65960196">For the question on Stack</a>, the user wanted to cache a call to Get-ADGroup for some automation at her workplace. Also, it was good enough to cache the membership when PowerShell opened up. So we started with looking at her code.</p>
<p>She already had a function called <code class="language-plaintext highlighter-rouge">Get-AdUsers</code>, which wrapped around the normal <code class="language-plaintext highlighter-rouge">Get-AdGroup</code> and <code class="language-plaintext highlighter-rouge">Get-AdUser</code> cmdlets, I’ll post a snippet here. The gist of this function was to retrieve all nested group members from a parent group.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-ADUsers</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
</span><span class="p">[</span><span class="n">Parameter</span><span class="p">(</span><span class="n">ValuefromPipeline</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">,</span><span class="w"> </span><span class="n">mandatory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">)][</span><span class="n">String</span><span class="p">]</span><span class="w"> </span><span class="nv">$GroupName</span><span class="w">
</span><span class="p">)</span><span class="w">
</span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$circular</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="c"># result holder</span><span class="w">
</span><span class="nv">$resultHolder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@()</span><span class="w">
</span><span class="nv">$table</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="nv">$nestedmembers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="nv">$adgroupname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="c"># get members of the group and member of</span><span class="w">
</span><span class="nv">$ADGroupname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get-adgroup</span><span class="w"> </span><span class="nv">$groupname</span><span class="w"> </span><span class="nt">-properties</span><span class="w"> </span><span class="nx">memberof</span><span class="p">,</span><span class="w"> </span><span class="nx">members</span><span class="w">
</span><span class="c"># list all members as list (no headers) and save to var</span><span class="w">
</span><span class="nv">$memberof</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$adgroupname</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">select</span><span class="w"> </span><span class="nt">-expand</span><span class="w"> </span><span class="nx">memberof</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$adgroupname</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$circular</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nv">$nestedMembers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ADGroupMember</span><span class="w"> </span><span class="nt">-Identity</span><span class="w"> </span><span class="nv">$GroupName</span><span class="w"> </span><span class="nt">-recursive</span><span class="w">
</span><span class="nv">$circular</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nv">$nestedMembers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ADGroupMember</span><span class="w"> </span><span class="nt">-Identity</span><span class="w"> </span><span class="nv">$GroupName</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">sort</span><span class="w"> </span><span class="nx">objectclass</span><span class="w"> </span><span class="nt">-Descending</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="c">#...code continued...</span><span class="w">
</span></code></pre></div></div>
<p>She was looking for some place to cache hits to LDAP. Two lines jumped out at me.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">13</span><span class="p">:</span><span class="w"> </span><span class="nv">$ADGroupname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get-adgroup</span><span class="w"> </span><span class="nv">$groupname</span><span class="w"> </span><span class="nt">-properties</span><span class="w"> </span><span class="nx">memberof</span><span class="p">,</span><span class="w"> </span><span class="nx">members</span><span class="w">
</span><span class="mi">20</span><span class="p">:</span><span class="w"> </span><span class="nv">$nestedMembers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ADGroupMember</span><span class="w"> </span><span class="nt">-Identity</span><span class="w"> </span><span class="nv">$GroupName</span><span class="w"> </span><span class="nt">-recursive</span><span class="w">
</span></code></pre></div></div>
<p>The top line is good to cache if we want this cmdlet to be fast when someone looks at multiple groups in a session.
The bottom line is good to cache when we want fast results when the parent group often contains the same nested groups.</p>
<h2 id="business-moment-coding-for-impact">Business Moment: Coding for Impact</h2>
<p>You might wonder at this point</p>
<blockquote>
<p>why bother analyzing the problem, I wanna code!</p>
</blockquote>
<p>This sort of analysis is good to perform before you just start coding. Ideally, our work should be done as part of a team, identifying pain-points and spending our efforts meaningfully.</p>
<p>You want at the end of the week, month and year to have a list of your achievements, and speeding up or improving the performance of something critical and meaningful will help you give your boss the ammo she needs to argue for a higher raise or promotion for you.</p>
<p>BusinessImpact image</p>
<p>TL/DR: Don’t waste engineering hours automating something painless or that no one cares about. Your efforts should be apparent and yell from the roof top “Yo, this Engineer is AWESOME, give him a raise!”</p>
<h3 id="inmemory--good-enough-caching">InMemory : Good Enough Caching</h3>
<p>We will begin by caching the nested hits, line 20. Most organizations go BONKERS nesting groups inside groups inside groups, so if we can mitigate some of those greedy LDAP hits to use our speedy, snappy cache will have an immediate speed boost.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">20</span><span class="p">:</span><span class="w"> </span><span class="nv">$nestedMembers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ADGroupMember</span><span class="w"> </span><span class="nt">-Identity</span><span class="w"> </span><span class="nv">$GroupName</span><span class="w"> </span><span class="nt">-recursive</span><span class="w">
</span></code></pre></div></div>
<p>We’ll do this by replacing this function call with another call. We’ll name this <code class="language-plaintext highlighter-rouge">Get-CachedADGroupMember</code></p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-CachedADGroupMember</span><span class="p">(</span><span class="nv">$groupname</span><span class="p">){</span><span class="w">
</span><span class="nv">$groupName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"cached_member_</span><span class="si">$(</span><span class="nv">$groupName</span><span class="si">)</span><span class="s2">"</span><span class="w">
</span><span class="nv">$cachedResults</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Variable</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Global</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$groupName</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w">
</span><span class="kr">if</span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$cachedResults</span><span class="p">){</span><span class="w">
</span><span class="s2">"found cached result"</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="nv">$cachedResults</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="p">{</span><span class="w">
</span><span class="s2">"need to cache"</span><span class="w">
</span><span class="nv">$results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get-adgroup</span><span class="w"> </span><span class="nv">$groupname</span><span class="w"> </span><span class="nt">-properties</span><span class="w"> </span><span class="nx">memberof</span><span class="p">,</span><span class="w"> </span><span class="nx">members</span><span class="w">
</span><span class="n">Set-CachedGroupMembership</span><span class="w"> </span><span class="nt">-groupName</span><span class="w"> </span><span class="nv">$groupName</span><span class="w"> </span><span class="nt">-value</span><span class="w"> </span><span class="nv">$results</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>This is pretty straightforward. The code builds the name of a variable and then checks the environment to see if it exists. If it does, that variable is returned. If not, then we execute the operation and hand off the results to another cmdlet just to store the results. The storage command is very simple. (Hint: it will become less simple once we add storage to disk!)</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">Function</span><span class="w"> </span><span class="nf">Set-CachedGroupMembership</span><span class="p">(</span><span class="nv">$groupName</span><span class="p">,</span><span class="nv">$value</span><span class="p">){</span><span class="w">
</span><span class="n">Set-Variable</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Global</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$groupName</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$value</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="nv">$value</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Already, this will become noticably faster because of all the cache hits. However, what if our cache becomes stale and we need to update it?</p>
<p>We can provide this feature by just passing a $boolean value of -Update in, by adding this to our cmds params.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-CachedADGroupMember</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="nv">$groupname</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">switch</span><span class="p">]</span><span class="nv">$update</span><span class="p">){</span><span class="w">
</span><span class="c">#...</span><span class="w">
</span><span class="kr">if</span><span class="p">((</span><span class="nv">$update</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$cachedResults</span><span class="p">)){</span><span class="w">
</span></code></pre></div></div>
<p>Then, to force an update of the Cache, we simply append <code class="language-plaintext highlighter-rouge">-Update</code> to our function.</p>
<p>Easy peasey!</p>
<h2 id="workselectxxspecializegeneralize">Work.Select(x=>x.Specialize.Generalize())</h2>
<p>Time to modify this function and remove the inherent AD Based focus, and turn it into a modular tool that could work to cache <em>anything</em>.</p>
<p>Some small edits is all we need!</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-CachedOperation</span><span class="p">([</span><span class="n">String</span><span class="p">]</span><span class="nv">$Name</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">ScriptBlock</span><span class="p">]</span><span class="nv">$command</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">Switch</span><span class="p">]</span><span class="nv">$Force</span><span class="p">){</span><span class="w">
</span><span class="nv">$CommandName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"cached_</span><span class="si">$(</span><span class="nv">$Name</span><span class="si">)</span><span class="s2">"</span><span class="w">
</span><span class="nv">$cachedResults</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Variable</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Global</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CommandName</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w">
</span><span class="kr">if</span><span class="p">(</span><span class="nv">$force</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$cachedResults</span><span class="w"> </span><span class="p">){</span><span class="w">
</span><span class="s2">"need to cache, evaluating..."</span><span class="w">
</span><span class="nv">$results</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$command</span><span class="o">.</span><span class="nf">Invoke</span><span class="p">()</span><span class="w">
</span><span class="n">New-Variable</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Global</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CommandName</span><span class="w"> </span><span class="nt">-value</span><span class="w"> </span><span class="nv">$results</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="p">{</span><span class="w">
</span><span class="s2">"found cached result"</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="nv">$cachedResults</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>To actually use it, we use the following</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">></span><span class="n">Get-CachedOperation</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">SlowCommand</span><span class="w"> </span><span class="nt">-command</span><span class="w"> </span><span class="p">([</span><span class="n">ScriptBlock</span><span class="p">]::</span><span class="n">Create</span><span class="p">({</span><span class="n">start-sleep</span><span class="w"> </span><span class="nx">2</span><span class="p">;</span><span class="kr">return</span><span class="w"> </span><span class="mi">5</span><span class="p">})</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">tee-object</span><span class="w"> </span><span class="nt">-var</span><span class="w"> </span><span class="nx">result</span><span class="w">
</span><span class="err">></span><span class="nv">$result</span><span class="o">.</span><span class="nf">Value</span><span class="w">
</span><span class="mi">5</span><span class="w">
</span></code></pre></div></div>
<p>One downside to our code as written is that there is no logic to rerun if the results get too stale</p>
<h2 id="always-fresh-powershell">Always Fresh PowerShell</h2>
<p>To add time awareness, the quickest way is to make a custom type that has the command name, scriptblock, results and an automatic timestamp. This is actually a perfect use case for PowerShell classes, which past me from like four years ago completely couldn’t understand. Aww, see how cute I was?</p>
<p>Link to my old post</p>
<p>So, here’s the class. We could make a new one if we wanted by running the bottom line.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span><span class="w"> </span><span class="nc">CachedOperation</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="c"># Automatic TimeStamp</span><span class="w">
</span><span class="p">[</span><span class="n">DateTime</span><span class="p">]</span><span class="w"> </span><span class="nv">$TimeStamp</span><span class="p">;</span><span class="w">
</span><span class="c"># Command Nickname</span><span class="w">
</span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$Name</span><span class="p">;</span><span class="w">
</span><span class="c"># Command Instructions</span><span class="w">
</span><span class="p">[</span><span class="n">ScriptBlock</span><span class="p">]</span><span class="w"> </span><span class="nv">$Command</span><span class="p">;</span><span class="w">
</span><span class="c"># Output, whatever it is</span><span class="w">
</span><span class="p">[</span><span class="n">psCustomObject</span><span class="p">]</span><span class="w"> </span><span class="nv">$Value</span><span class="p">;</span><span class="w">
</span><span class="c">#Constructor</span><span class="w">
</span><span class="n">CachedOperation</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$name</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">ScriptBlock</span><span class="p">]</span><span class="nv">$scriptblock</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="bp">$this</span><span class="o">.</span><span class="nf">TimeStamp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">UtcNow</span><span class="w">
</span><span class="bp">$this</span><span class="o">.</span><span class="nf">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$name</span><span class="p">;</span><span class="w">
</span><span class="bp">$this</span><span class="o">.</span><span class="nf">Command</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$scriptblock</span><span class="w">
</span><span class="bp">$this</span><span class="o">.</span><span class="nf">Value</span><span class="o">=</span><span class="w"> </span><span class="nv">$scriptblock</span><span class="o">.</span><span class="nf">Invoke</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Now to modify the function to work with this class.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Get-CachedOperation</span><span class="p">([</span><span class="n">String</span><span class="p">]</span><span class="nv">$Name</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">ScriptBlock</span><span class="p">]</span><span class="nv">$Command</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">Switch</span><span class="p">]</span><span class="nv">$Force</span><span class="p">){</span><span class="w">
</span><span class="nv">$CommandName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"cached_</span><span class="si">$(</span><span class="nv">$Name</span><span class="si">)</span><span class="s2">"</span><span class="w">
</span><span class="nv">$cachedResults</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Variable</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Global</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CommandName</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Select</span><span class="w"> </span><span class="nt">-ExpandProperty</span><span class="w"> </span><span class="nx">Value</span><span class="w">
</span><span class="kr">if</span><span class="p">(</span><span class="nv">$force</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$cachedResults</span><span class="w"> </span><span class="p">){</span><span class="w">
</span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"need to cache, evaluating..."</span><span class="w">
</span><span class="nv">$CachedOperation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">CachedOperation</span><span class="p">]::</span><span class="n">new</span><span class="p">(</span><span class="nv">$Name</span><span class="p">,</span><span class="w"> </span><span class="nv">$command</span><span class="p">)</span><span class="w">
</span><span class="n">New-Variable</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Global</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$CommandName</span><span class="w"> </span><span class="nt">-value</span><span class="w"> </span><span class="nv">$CachedOperation</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="nv">$cachedResults</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$CachedOperation</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">else</span><span class="p">{</span><span class="w">
</span><span class="n">Write-Verbose</span><span class="w"> </span><span class="s2">"found cached result"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="kr">return</span><span class="w"> </span><span class="nv">$cachedResults</span><span class="o">.</span><span class="nf">Value</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>And in action:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Get-CachedOperation -Name MySlowCommand -command ([ScriptBlock]::Create({start-sleep 1;return 6}) )
VERBOSE: need to cache, evaluating...
6
PS C:\Users\Stephen> Get-CachedOperation -Name MySlowCommand -command ([ScriptBlock]::Create({start-sleep 1;return 6}) )
VERBOSE: found cached result
6
</code></pre></div></div>
<p>So in the last step, no value was created. We were merely setting up the scaffolding for the next, actually cool step.</p>
<p>Seems silly to have intermediate steps but in the real world, you’ll probably be following a flow like this, creating the scaffolding and supporting functions and then submitting them with their unit tests. Then once that passes muster, you introduce the small feature flag PR that flips things around and starts using that new code.</p>
<h2 id="adding-staleness-check">Adding Staleness Check</h2>
<p>To actually do something useful, let’s add the time check to see how old the results are.</p>
<p>This is pretty easily done, when we retrieve a cached result, we’ll check to see how old it is and if more than 30 minutes, we’ll rerun the operation.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
function Get-CachedOperation([String]$Name, [ScriptBlock]$Command, [Switch]$Force){
$CommandName = "cached_$($Name)"
$cachedResults = Get-Variable -Scope Global -Name $CommandName -ErrorAction SilentlyContinue | Select -ExpandProperty Value
if($force -or $null -eq $cachedResults -or ($cachedResults.TimeStamp -le [DateTime]::UtcNow.AddMinutes(-2))){
if($cachedResults.TimeStamp -le [DateTime]::UtcNow.AddMinutes(-2)){
Write-Verbose "Results are too old, reevaluating..."
}
else{
Write-Verbose "need to cache, evaluating..."
}
$CachedOperation = [CachedOperation]::new($Name, $command)
New-Variable -Scope Global -Name $CommandName -value $CachedOperation -Force
$cachedResults = $CachedOperation
}
else{
Write-Verbose "found cached result"
}
return $cachedResults.Value
}
</code></pre></div></div>
<h2 id="to-disk-and-beyond">To Disk, and Beyond*</h2>
<Sad Buzz="" Lightyard="">
*We will not actually be going beyond in this post
Like this post? Great! Join us next time where we'll persist the results to disk!
</Sad>Stephen OwenThe other day I answered a question on StackOverflow about how to cache the results of slow running operations easily in PowerShell. I thought it had the makings of a good blog post so here we go!PowerShell quickie - function to make your Mocks faster2020-08-14T00:00:00+00:002020-08-14T00:00:00+00:00https://www.foxdeploy.com/blog/powershell-quickie-function-to-make-your-mocks-faster<p><img src="../assets/images/2020/08/images/quicker-auto-mocking-in-c.png" alt="" /> In C#, writing unit tests is king, and Moq is the hotness we use to Mock objects and methods, like the MockObjects we get with Pester in PowerShell.</p>
<p>But one rough part of it is the syntax for Moq, which requires you to write a handler and specify each input argument, which can get pretty verbose and tiresome.
<!--more-->
To ease this up, try this function, which will take a method signature and convert it into a sample Mock.Setup or Mock.Verify block, ready for testing</p>
<script src="https://gist.github.com/4f60a5739c40a7541006cce153fc4194.js"> </script>Stephen OwenIn C#, writing unit tests is king, and Moq is the hotness we use to Mock objects and methods, like the MockObjects we get with Pester in PowerShell.DIY Microsoft Teams On-Air Light!2020-07-28T00:00:00+00:002020-07-28T00:00:00+00:00https://www.foxdeploy.com/blog/diy-microsoft-teams-on-air-light<p>Children. You love them. They in turn, run into your meetings all the time. Sometimes wearing pants.</p>
<p>Wouldn’t it be great to have a way to keep them informed of when Daddy or Mommy is in a meeting? Something nice and big and obvious that they can just totally ignore, right?</p>
<p>That’s why I sought to design my own perfect on-air light, to automatically turn on when I joined Teams Meetings. Won’t you join me in this journey together, and you can build your own?</p>
<p><img src="../assets/images/2020/07/images/teams-on-airlight.png" alt="Banner graphic, says 'How-To guide, On-air light' and depicts a light with red letters that say 'ON-AIR', illuminated and hanging above a door frame" /></p>
<h2 id="why-make-one">Why make one?</h2>
<p>Great question, and if either of these describe you, you can probably just stop and buy one of the off the shelf products that answer this need.</p>
<ul>
<li>I don’t have a closed door I can work behind.</li>
<li>It would be ok to just have something for my desk</li>
<li>I enjoy inflicting my children upon others</li>
</ul>
<p>But if you do want to make your own…read on!</p>
<h3 id="you-will-need">You will need…</h3>
<ul>
<li>Wemo Smart Switch (I’m using the small rectangular ones)</li>
<li>Wemo App installed on a device</li>
<li>A free Account for IFTT</li>
<li>The Lync 2013 SDK (just the `Microsoft.Lync.Model.dll` to be precise)</li>
<li>A suitable Lightbulb</li>
<li>A very talented partner to lovingly fashion your On-Air light for you!</li>
</ul>
<p>With all of the products acquired, let’s get started.</p>
<h3 id="setting-up-the-smart-switch">Setting up the Smart Switch</h3>
<p>This can be surprisingly hard. If you buy the three or five packs of the Wemo Mini Smart Switch, rectangular style, they will likely be the Mini.82C or F7C063. Depending on your luck and if you buy the bulk packaging, you might end up with ones like I got, which had Firmware so old the Wemo Smart app as of July 2020 would be unable to configure them.</p>
<p>If that happens to you, here’s how to get them going.</p>
<ol>
<li>Plug in Smart Switch</li>
<li>On your phone, disable automatic Wireless Switching in your WiFi settings (this is on you to find, but it’s probably under Advanced settings)</li>
<li>When the Switch blinks White/Orange, connect directly to its WiFi network manually.</li>
<li>Now open the Wemo App.</li>
<li>It will launch in the ‘Add new device’ experience, so proceed to now connect as usual, give the device a good name, then update the Firmware.</li>
</ol>
<p>Do this for each switch to make your life easier.</p>
<p>I’m calling my device ‘MeetingLight’.</p>
<p><strong>Before moving on</strong>, you should have one plug connected to a light or fan or whatever that responds when you turn it on and off with the Wemo app.</p>
<p>As a sneak preview, this is what turning a light on from PowerShell when I join a meeting…here’s what it looks like!</p>
<h3 id="connecting-switches-to-ifttt">Connecting Switches to IFTTT</h3>
<p>If-This-Then-That is an awesome resource, an automation engine that provides endless capabilities and is really amazing and wonderful.</p>
<p>I like it. I like it a lot.
https://www.youtube.com/watch?v=FD2qrBRy84k&feature=emb_logo</p>
<p>In this section, we’ll create a new flow we can use that starts with a Web Push and ends with asking Wemo nicely to do something for us.</p>
<p>Login to <a href="https://ifttt.com">https://ifttt.com</a>/ and click on Create.</p>
<p><img src="../assets/images/2020/07/images/create.png" alt="Shows the If this then that user interface, with a box highlighting the word 'Create'" /></p>
<p>Click on ‘This’ and choose ‘Webhooks’:
<img src="../assets/images/2020/07/images/create-01.png" alt="" /></p>
<ul>
<li>You actually do click the plus sign or the word This!*</li>
</ul>
<p>This is the icon you are looking for.</p>
<p><img src="../assets/images/2020/07/images/create-02.png" alt="Shows a textbox with the word 'webhook' entered and below it a large picture which also says webhook." /> The Webhook logo is so pretty![/caption]</p>
<p>Select ‘Recieve a web request’</p>
<p><img src="../assets/images/2020/07/images/create-02.2.png" alt="Show the IFTTT UI, and a box with the text 'Receieve a web request' is displayed, which says the automation will be triggered whenever a web request occurs" /> This is so cool![/caption]</p>
<p>Next, choose ‘That’, where we’ll tell IFTTT what to do when this flow happens.</p>
<p><img src="../assets/images/2020/07/images/create-03.png" alt="Shows the If this then that logo again, but now the IF contains the webhook logo, showing this flow begins with a webhook" /> The User interface speaks to me! See, it’s the same logo but now it calls out that the flow begins with a Webhook. Excellent UX.</p>
<p>Search for Wemo Smart Plug and you’ll have to login to an oAuth process to connect the services together.</p>
<p><img src="../assets/images/2020/07/images/create-03.1.png" alt="the IFTTT ui, now with the heading 'Choose action service', and in the text box to search, Wemo Smart was entered. The only option is the Wemo smart plug as the action to trigger." /> You’d pick your smart bulb, fan or crockpot if you were turning those on and off when entering a meeting…</p>
<p>Hm, maybe a flow to trigger my George Forman grill to make some bacon for me?</p>
<p><img src="../assets/images/2020/07/images/create-03.2.png" alt="Shows a list of possible wemo actions, including Turn On, Turn Off, Turn on then off, and Toggle back and forth" /> There are a lot of possibilites here!</p>
<h3 id="connecting-to-the-device">Connecting to the Device</h3>
<p>Now, pick the smart device we setup way back in section one to enact the action upon.</p>
<p><img src="../assets/images/2020/07/images/create-03.3.png?w=636" alt="" /></p>
<p>I am picking ‘Meeting light’.</p>
<p><img src="../assets/images/2020/07/images/create-finish.png?w=636" alt="Shows the final step of the IFTTT flow, confirming the starting and stopping points of the flow and has a large 'FINISH" button at the bottom." /></p>
<p>Finally, click ‘Finish’ on the review and finish page, and go ahead and try it out to confirm your flow works.</p>
<p>💡 Do this one more time to setup a ‘TurnOffTheLight’ flow too! 🤓</p>
<p>My two flows are named <code class="language-plaintext highlighter-rouge">meetingStart</code> and <code class="language-plaintext highlighter-rouge">meetingStop</code>.</p>
<h3 id="retrieve-the-url-and-convert-to-a-powershell-function">Retrieve the URL and convert to a PowerShell Function</h3>
<p>This part is so easy, still within IFTTT, click on ‘Documentation’ from the <a href="https://ifttt.com/maker_webhooks">Maker:Webhooks</a> page.</p>
<p><img src="../assets/images/2020/07/images/create-url-01.png?w=636" alt="Depicts the maker\webhooks page and shows a box drawn around the large 'Documentation' button on the corner. " /> Clicking here on the Documentation button shows you how to formulate your requests to IFTTT.[/caption]</p>
<p>I only drew a big box in the screen shot because I, embarrassingly, just couldn’t find it! The next page shows you your API key and how to trigger your events.</p>
<p><img src="../assets/images/2020/07/images/create-url-02.png?w=636" alt="shows a heavily redacted screenshot of the IFTTT UI page, where the URL format needed is displayed to trigger flows. They are in the format of https://maker.ifttt.com/trigger/{event name goes here/with/key/{API key goes here}" /></p>
<p>This will show you how to formulate your request and the URL to hit.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://maker.ifttt.com/trigger//with/key/
</code></pre></div></div>
<blockquote>
<p>But aren’t these tokens in the clear?</p>
</blockquote>
<p> </p>
<p>No they are not. With HTTPS, as we have discussed before on this blog in <a href="http://foxdeploy.com/2016/09/16/winrm-https-and-the-case-of-ghost-certificate/">The Case of the Spooky Certificate</a>, even the URL itself is secured and passed as an encrypted body parameter. Only the target server, in this case maker.ifttt.com is transmitted in clear.</p>
<p>Now let’s make these into the worlds ugliest PowerShell functions.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Function meetingStart { irm https://maker.ifttt.com/trigger/meetingStart/with/key/apiKeyGoesHere -method Post }
Function meetingStop { irm https://maker.ifttt.com/trigger/meetingStop/with/key/apiKeyGoesHere -method Post }
</code></pre></div></div>
<h2 id="connecting-to-microsoft-teams">Connecting to Microsoft Teams</h2>
<p>Here you will need the <a href="https://www.microsoft.com/en-us/download/details.aspx?id=36824">Microsoft Lync 2013 SDK</a>. You don’t have to install it, just open the .exe with 7Zip then manually run the x86 flavored <code class="language-plaintext highlighter-rouge">.msi</code>.</p>
<p>Or if you’re <em>really cool</em>, extract that too and just get this dll file, <code class="language-plaintext highlighter-rouge">Assemblies\Desktop\Microsoft.Lync.Model.dll</code>.</p>
<p>You can also just search for it on the web, some folks bundle it on Github with their projects.</p>
<p><strong><em>Once you have that…</em></strong></p>
<p>As of this writing, retrieving user presence through the Graph API requires special permissions. Some tenants, like your companies Office 365 tenant might allow regular users a token to retrieve delegated info, but not all tenants do this. If they don’t then you may require Tenant Admin permissions to hit the Graph API and get presence state back. I felt that was kind of overkill to turn on a light, if you ask me so I looked to other options.</p>
<blockquote>
<p>Wait, what is user presence?</p>
</blockquote>
<p>It’s the the Office Unified Communications (sometimes called Office UC) term for being Away, Present, Presenting).</p>
<p>Next up, I had poor luck using the modern OfficeUC SDK to connect directly to Teams to retrieve the status and gave up after a few hours, in the interests of staying true to the ‘pressures on’ hackathon spirit.</p>
<p>So, to retrieve status, we will query it from Skype4Business! How elegant, right?</p>
<h3 id="getting-into-it">Getting into it</h3>
<p>The root of our woes is that the presence of a person is protected info, and rightly so. Imagine if a vendor knew the second you sat down at your desk and could call you every time. It would get old, and fast.</p>
<p>To be trusted with presence info, apps like Office, Teams and Skype all had to do some heavy lifting to retrieve and set our Presence state, and we can only view that info about peers if we authenticate and use our account, or are federated, which means using an account. Again, heavy lifting.</p>
<p>So, in order for us to do it in code, here’s what we can do.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Add-Type -Path "C:\Program Files (x86)\Microsoft Office 2013\LyncSDK\Assemblies\Desktop\Microsoft.Lync.Model.dll";
#Gets a reference to the currently running Skype4Business client $lyncclient = [Microsoft.Lync.Model.LyncClient]::GetClient()
#Gets a reference to our special contact object from Skype $myContact = $lyncclient.Self.Contact;
#Calls our contact to update the status and retrieve an \`Availability\` property back $myState = $myContact.GetContactInformation("Availability")
</code></pre></div></div>
<p>See, even retrieving our own state results in a call that Lync/Skype4Business processes for us.</p>
<p>But it works! Now to bake the whole thing into some code to run…
<script src="https://gist.github.com/d9284559742a832fdb2e7bd190a62da7.js"> </script></p>
<p>And it works! When I join a call or a meeting, in just a few moments, the light outside my door turns on!</p>
<p><img src="../assets/images/2020/07/images/20200728_112406.jpg?w=636" alt="Shows a make-shift 'on-air' light of the kind you would find in a news radio booth to indicate the host is live on air." /> Ain’t she a beaut![/caption]</p>
<p>I realize that my instructions on how to actually make the On Air light fixture are akin to this.</p>
<p><img src="../assets/images/2020/07/images/howtodrawanowl.jpeg?w=636" alt="a humorous image showing how to draw an owl in two steps. The first step is two simple circles. The next step shows an incredibly ornate drawing of an owl with the instructions 'now draw the rest of the damn owl'" /></p>
<p>My wife made the whole thing for me! She used a leftover children’s crafting lunchbox and some black and red vinyl for the graphic, which she cut out using a Cricut machine.</p>
<h3 id="whats-next">What’s next?</h3>
<p>I’ll update this as I find better ways to do it, of course. Wait, before you leave, do you know of a better way!?</p>
<p>Share it in the comments or on our subreddit! Did you make your own? I’d love to see it!</p>
<p>Tag me on Twitter @FoxDeploy and I’ll retweet the coolest on-air lights folks create.</p>Stephen OwenChildren. You love them. They in turn, run into your meetings all the time. Sometimes wearing pants. Wouldn't it be great to have a way to keep them informed of when Daddy or Mommy is in a meeting? Something nice and big and obvious that they can just totally ignore, right?Joining Microsoft2020-05-18T00:00:00+00:002020-05-18T00:00:00+00:00https://www.foxdeploy.com/blog/joining-microsoft<p><img src="../assets/images/2020/05/images/azureatl.png?w=636" alt="Picture of the author in front of the Microsoft Logo sign in Redmond Washington on the microsoft campus" /></p>
<p>I have really loved these last three years with #BigBank #SpoilersItWasWellsFargoAllAlong and made some great friends and had some awesome experiences creating and sharing sessions at MMS with my friends I made along the way.</p>
<p>My career for the last ten years has been focused on automating, deploying, and managing Microsoft technologies. And now now, I’m going to get a chance to help work on them as well!</p>
<p>Starting May 18th, I am happily joining Microsoft’s Azure Compute team as a Developer. I’ll be remaining in Atlanta, and working from home for the foreseeable future.</p>
<p>What to Expect</p>
<p>This blog has always been a place for me to show you how I do it and I will continue to do the same thing, with my own same flavor and perspective. All thoughts and perspectives will be my own and will not be my employers.</p>
<p>I’ll update this blog in the coming weeks when I have tips to share about what I’ve been working on, or as post ideas strike me!</p>Stephen OwenI have really loved these last three years with #BigBank #SpoilersItWasWellsFargoAllAlong and made some great friends and had some awesome experiences creating and sharing sessions at MMS with my friends I made along the way.Progressive Automation Pt II - PowerShell GUIs2020-05-08T00:00:00+00:002020-05-08T00:00:00+00:00https://www.foxdeploy.com/blog/progressive-automation-pt-ii-powershell-guis<p><a href="/series/LearningGUIs"><img src="../series/images/series_gui.webp" alt="Learning PowerShell GUIs" /></a></p>
<p><strong>This post is part of the Learning GUI Toolmaking Series, here on FoxDeploy. Click the banner to return to the series jump page!</strong></p>
<hr />
<h2 id="progressive-automation-pt-ii---powershell-guis">Progressive Automation Pt II - PowerShell GUIs</h2>
<p><img src="../assets/images/2020/05/images/progressive-automation-pt-ii.png" /></p>
<p><a href="http://foxdeploy.com/2019/09/10/progressive-automation-part-i/">In our previous post in the series</a>, we took a manual task and converted it into a script, but our users could only interface with it by ugly manual manipulation of a spreadsheet. And, while I think sheetOps <a href="https://twitter.com/danielepolencic/status/1254330583380979712">(configuring and managing a Kubernetes cluster with a GoogleSheets doc!)</a> are pretty cool we can probably do better.</p>
<p>So in this post, I’ll show how I would typically go about building a PowerShell WPF GUI from an existing automation that kind of works OK.</p>
<h3 id="analysis">Analysis</h3>
<p>To begin making a UI we need to start by analyzing which values a user will be entering, considering what inputs make sense for that, and then thinking if there is anything the user will need to see in the UI as well, so, looking back to the first post…</p>
<blockquote>
<p>To begin with, users have their own spreadsheet they update like this, it’s a simple CSV format.</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HostName,Processed,ProcessedDate
SomePC123
SomePC234
SomePC345
</code></pre></div></div>
<p>Previous our users were manually adding computers to a list of computer names. That kind of scenario is best handled by the TextBox input. Or if we hate our users, we can make them provide input with a series of sliders.</p>
<p>Me: The ideal phone number input control doesn’t exis– [wpvideo 8HfEBT6J] <a href="https://twitter.com/RandomNoun7/status/1256961724936097792">Gif Credit - Twitter</a></p>
<p>So we need at least a TextBox.</p>
<p>We need a confirmation button too, to enter the new items. We also need some textblocks to explain the UI. Finally, a Cancel/Reset button to zero out the text box.</p>
<p>We should also provide feedback of how many items we see in their input, so we should add a label which we can update.</p>
<p>That brings us up to:</p>
<ul>
<li><strong>Inputs</strong>
<ul>
<li>TextBox for ComputerNames</li>
<li>Buttons
<ul>
<li>OK</li>
<li>Cancel</li>
</ul>
</li>
</ul>
</li>
<li><strong>Display Elements</strong>
<ul>
<li> Welcome / Intro Text</li>
<li> Confirmation Area</li>
<li> Updatable Label to show count for devices input</li>
<li>DataGrid to show current contents</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>A note on TextBoxes:</strong> As soon as we provide TextBoxes to users, all kinds of weird scenarios might happen. Expect it!</p>
</blockquote>
<p>For instance, users will copy and paste from e-mails in Outlook, or from Spreadsheets in Excel. They might also type in notepad a list of computers separated by Newlines (<code class="language-plaintext highlighter-rouge">/r/n</code>) carriage returns. Or maybe they’re more of the comma-separated type, and will try to separate entries with Commas. These are all predictable scenarios we should account for in our UI, so we should give the user some kind of confirmation of what we see from their typing in the TextBox, and our form should handle most of the weird things they’ll try.</p>
<p><strong>That’s why we need Confirmation.</strong> If you provide UI without confirmation, users will hate you and e-mail (or worse, they might call you!!) for help, so be sure to do it the right way and think of their needs from the get go, or you will enjoy getting to hear from them a lot.</p>
<p>Don’t make UI that will make your users hate you, like this one ![](</p>
<p>https://twitter.com/FoxDeploy/status/1256953579186905090</p>
<p>With all of these components in mind, time to get started.</p>
<h3 id="making-the-thing">Making the thing</h3>
<p>We’re going to open up Visual Studio, pick a WPF app and then do some drag and dropping. If you are getting a bit scared of how to do it, or what you should do to install it, check out some of the previous posts in my GUI Series, here!</p>
<p><img src="http://foxdeploy.com/series/Learning-gui-toolmaking-series/" alt="" /></p>
<p>You should end up with something like this:
<script src="https://gist.github.com/7ca9e036f29a97bbfafae2cbd485cc9b.js"> </script></p>
<p>Which will look like this when rendered!</p>
<p><img src="../assets/images/2020/05/images/resultui.png" alt="Shows a pretty ugly UI" /> Easily the ugliest UI we’ve done so far[/caption]</p>
<p>To wire up the buttons, I wrote a few helped functions for the logic for the buttons, which look like this.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">loadListView</span><span class="p">(){</span><span class="w"> </span><span class="nv">$</span><span class="nn">global</span><span class="p">:</span><span class="nv">deviceList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new-object</span><span class="w"> </span><span class="nt">-TypeName</span><span class="w"> </span><span class="nx">System.Collections.ArrayList</span><span class="w"> </span><span class="nv">$devices</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">import-csv</span><span class="w"> </span><span class="s2">"</span><span class="bp">$PSScriptRoot</span><span class="s2">\\devices.csv"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Sort-Object</span><span class="w"> </span><span class="nx">Processed</span><span class="w"> </span><span class="nx">ForEach</span><span class="p">(</span><span class="nv">$device</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$devices</span><span class="p">){</span><span class="w"> </span><span class="nv">$</span><span class="nn">global</span><span class="p">:</span><span class="nv">deviceList</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nv">$device</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="nv">$WPFdevice</span><span class="n">\_listView.ItemsSource</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$</span><span class="nn">global</span><span class="p">:</span><span class="nv">deviceList</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kr">function</span><span class="w"> </span><span class="nf">cancelButton</span><span class="p">(){</span><span class="w"> </span><span class="nv">$WPFok</span><span class="o">.</span><span class="nf">IsEnabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w"> </span><span class="nv">$wpfdeviceTextbox</span><span class="o">.</span><span class="nf">Text</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="nv">$wpflabelCounter</span><span class="o">.</span><span class="nf">Text</span><span class="o">=</span><span class="s2">"Reset"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$wpfdeviceTextbox</span><span class="o">.</span><span class="nf">Add</span><span class="n">\_TextChanged</span><span class="p">({</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$wpfdeviceTextbox</span><span class="o">.</span><span class="nf">Text</span><span class="o">.</span><span class="nf">Length</span><span class="w"> </span><span class="o">-le</span><span class="w"> </span><span class="mi">5</span><span class="p">){</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="nv">$WPFok</span><span class="o">.</span><span class="nf">IsEnabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w"> </span><span class="nv">$deviceTextbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$wpfdeviceTextbox</span><span class="o">.</span><span class="nf">Text</span><span class="o">.</span><span class="nf">Split</span><span class="p">(</span><span class="s1">','</span><span class="p">)</span><span class="o">.</span><span class="nf">Split</span><span class="p">(</span><span class="nx">\</span><span class="p">[</span><span class="n">System.Environment\</span><span class="p">]::</span><span class="n">NewLine</span><span class="p">)</span><span class="o">.</span><span class="nf">Where</span><span class="p">({</span><span class="err">$</span><span class="n">\_.Length</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nx">3</span><span class="p">})</span><span class="w"> </span><span class="nv">$count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$deviceTextbox</span><span class="o">.</span><span class="nf">Count</span><span class="w"> </span><span class="nv">$wpflabelCounter</span><span class="o">.</span><span class="nf">Text</span><span class="o">=</span><span class="nv">$count</span><span class="w"> </span><span class="p">})</span><span class="w">
</span><span class="nv">$WPFCancel</span><span class="o">.</span><span class="nf">Add</span><span class="n">\_Click</span><span class="p">({</span><span class="w"> </span><span class="n">cancelButton</span><span class="w"> </span><span class="p">})</span><span class="w">
</span><span class="nv">$WPFok</span><span class="o">.</span><span class="nf">Add</span><span class="n">\_Click</span><span class="p">({</span><span class="w"> </span><span class="nv">$deviceTextbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$wpfdeviceTextbox</span><span class="o">.</span><span class="nf">Text</span><span class="o">.</span><span class="nf">Split</span><span class="p">(</span><span class="s1">','</span><span class="p">)</span><span class="o">.</span><span class="nf">Split</span><span class="p">(</span><span class="nx">\</span><span class="p">[</span><span class="n">System.Environment\</span><span class="p">]::</span><span class="n">NewLine</span><span class="p">)</span><span class="o">.</span><span class="nf">Where</span><span class="p">({</span><span class="err">$</span><span class="n">\_.Length</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nx">3</span><span class="p">})</span><span class="w"> </span><span class="n">ForEach</span><span class="p">(</span><span class="nv">$item</span><span class="w"> </span><span class="n">in</span><span class="w"> </span><span class="nv">$deviceTextbox</span><span class="p">){</span><span class="w"> </span><span class="nv">$</span><span class="nn">global</span><span class="p">:</span><span class="nv">deviceList</span><span class="o">.</span><span class="nf">Add</span><span class="p">(</span><span class="nx">\</span><span class="p">[</span><span class="n">pscustomObject\</span><span class="p">]@{</span><span class="nx">HostName</span><span class="o">=</span><span class="nv">$item</span><span class="p">})</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="n">set</span><span class="nt">-content</span><span class="w"> </span><span class="s2">"</span><span class="bp">$PSScriptRoot</span><span class="s2">\\devices.csv"</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="err">$</span><span class="p">(</span><span class="nv">$deviceList</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ConvertTo</span><span class="nt">-csv</span><span class="w"> </span><span class="nt">-NoTypeInformation</span><span class="p">)</span><span class="w"> </span><span class="n">cancelButton</span><span class="w"> </span><span class="n">loadListView</span><span class="w"> </span><span class="p">})</span><span class="w">
</span></code></pre></div></div>
<p>To walk through these, we set an arrayList to track our collection of devices from the input file in <code class="language-plaintext highlighter-rouge">loadListView</code>, then define behavior in the <code class="language-plaintext highlighter-rouge">$WPFok.Add_Click</code> method to save the new items to the output.csv file. This is simple, and much harder to mess up than our previous approach of telling users to update a <code class="language-plaintext highlighter-rouge">.csv</code> file manually.</p>
<p><a href="https://gist.github.com/1RedOne/377f4c0d1ae209844f7f34ce9ecf581e#file-foxdeploy_newgui-xaml">🔗Get the complete source here 🔗</a></p>
<h3 id="wait-wheres-the-beefxaml-files">Wait, where’s the beef XAML Files?</h3>
<p>You may also notice a new method of loading up the <code class="language-plaintext highlighter-rouge">.XAML</code> files.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">void\</span><span class="p">]</span><span class="n">\</span><span class="p">[</span><span class="n">System.Reflection.Assembly\</span><span class="p">]::</span><span class="n">LoadWithPartialName</span><span class="p">(</span><span class="s1">'presentationframework'</span><span class="p">)</span><span class="w">
</span><span class="nv">$xamlPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="si">$(</span><span class="bp">$PSScriptRoot</span><span class="si">)</span><span class="s2">\\</span><span class="si">$(</span><span class="p">(</span><span class="n">split-path</span><span class="w"> </span><span class="bp">$PSCommandPath</span><span class="w"> </span><span class="nt">-Leaf</span><span class="w"> </span><span class="p">)</span><span class="o">.</span><span class="nf">Split</span><span class="p">(</span><span class="s2">"."</span><span class="p">)</span><span class="n">\</span><span class="p">[</span><span class="mi">0</span><span class="n">\</span><span class="p">])</span><span class="o">.</span><span class="nf">xaml</span><span class="s2">" if (-not(Test-Path </span><span class="nv">$xamlPath</span><span class="s2">)){ throw "</span><span class="n">Ensure</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="nv">$xamlPath</span><span class="w"> </span><span class="n">is</span><span class="w"> </span><span class="n">present</span><span class="w"> </span><span class="n">within</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="s2">" } </span><span class="bp">$input</span><span class="s2">XML = Get-Content </span><span class="nv">$xamlPath</span><span class="s2"> </span><span class="bp">$input</span><span class="s2">XML = </span><span class="bp">$input</span><span class="s2">XML -replace 'mc:Ignorable="</span><span class="n">d</span><span class="s2">"','' -replace "</span><span class="nx">x:N</span><span class="s2">",'N' -replace '^<Win.\*', '<Window' \[xml\]</span><span class="nv">$XAML</span><span class="s2"> = </span><span class="bp">$input</span><span class="s2">XML \[/code\]
</span></code></pre></div></div>
<p>After some time away from writing PowerShell GUIs, I now think it is unnecessarily verbose to keep your <code class="language-plaintext highlighter-rouge">.xaml</code> content within the script, and now recommend letting your <code class="language-plaintext highlighter-rouge">xaml</code> layouts live happily next to the script and logic code. So I’ve modified the template as shown here, to now automatically look for a matching named <code class="language-plaintext highlighter-rouge">.xaml</code> file within the neighboring folder. Simple and easy to read!</p>
<h4 id="next-time">Next time</h4>
<p>And that’s that! Was this the world’s best GUI? Yes. Yes of course it was!</p>
<p><strong>Join us next time where we explore a whole new world,</strong> don’t you dare close your eyes, of aspnet core as an alternative way of approaching automation.</p>
<p>If you’re still looking for something to do, <a href="https://www.bagaar.be/insights/user-inyerface">try this out this great walkthrough of terrible UI traits</a> by a UI design consulting firm. Whatever you do, don’t do this in your UI and you’ll be off to a good start.</p>Stephen OwenIn our previous post in the series, we took a manual task and converted it into a script, but our users could only interface with it by ugly manual manipulation of a spreadsheet. So in this post, I'll show how I would typically go about building a PowerShell WPF GUI from an existing automation that kind of works OK.Quick Guide - Setting up Remote Management of your child’s PC2020-03-18T00:00:00+00:002020-03-18T00:00:00+00:00https://www.foxdeploy.com/blog/quick-guide-setting-up-remote-management-of-your-childs-pc<h2 id="quick-guide---setting-up-remote-management-of-your-childs-pc">Quick Guide - Setting up Remote Management of your child’s PC</h2>
<p><img src="../assets/images/2020/03/images/managing-your.png" alt="MANAGING YOUR" /></p>
<p>With everyone working remote now, it’s really helpful to have a method to remote control your kid’s computers, <em>especially</em> if they are hard to keep on task like mine.</p>
<p>So I wrote this short guide to help you get a handle. <strong>This guide expects you to have two computers, one for you, one for your kids to use.</strong></p>
<h3 id="whoa-i-need-a-computer-for-the-kids">Whoa I need a computer for the kids?</h3>
<p>This guide is only going to cover PCs, not tablets. Sorry.
<!--more-->
If you need to buy one, this is what I’m now recommending. Walmart has a new in-store brand of computers which is surprisingly great for the money, called their Motile line. It can do light to moderate gaming like Minecraft and Fortnite, and also handle video editing if you’ve got a budding YouTuber, as well as programming. And the best point? It’s user upgradable so you can add more RAM or get a bigger hard drive down the road.</p>
<p><img src="../assets/images/2020/03/images/walmartmotile.png" alt="walmartMotile" /></p>
<p><a href="../assets/images/2020/03/https://www.walmart.com/ip/Vega-HDMI-Front-RAM-FHD-4GB-display-AMD-128GB-Tuned-Camera-Radeon-14-Ryzen-720P-THX-Graphics-IR-SSD-Silver-Laptop-3-MOTILE-Spatial-Performance-Audio-/715635402?selected=true&irgwc=1&sourceid=imp_ylrx6S3oMxyOU1UwUx0Mo38QUknSpzwNr2JTSQ0&veh=aff&wmlspartner=imp_2098076&clickid=ylrx6S3oMxyOU1UwUx0Mo38QUknSpzwNr2JTSQ0">Motile 14” AMD Laptop with Radeon 3 Graphics, 128 GB SSD and 4GB RAM</a></p>
<p><a href="../assets/images/2020/03/https://www.youtube.com/watch?v=0hMdQAjy43A">Linus Tech Tips did a great and funny video on this laptop too if you’re interested.</a></p>
<p>/\ The above are <strong>not my</strong> affiliate links.</p>
<blockquote>
<p>Note: a word about Remote Management</p>
</blockquote>
<p><em>This method will setup Remote Viewing and control of your child’s computer</em></p>
<p>It is <strong>imperative that you treat your children with maturity, allow them breaks and make sure they know you can remote into their PC.</strong></p>
<p>You don’t want to be stuck at home with kids who feel you’ve abused their trust by spying on them. Only use these powers for good.</p>
<p>If you disagree with this, I don’t care so please keep that perspective to yourself.</p>
<h3 id="how-to-enable-remote-management">How to enable Remote Management</h3>
<p>On the child’s computer, login using the administrator’s account.</p>
<p>This is simple, just login using the main account, our goal here is to setup an account for our homeschooler child to use which will have restricted permissions.</p>
<p>Hit Start, then type ‘User Accounts’, then <strong>Add, edit or remove other users.</strong></p>
<p><img src="../assets/images/2020/03/images/remote01.png" alt="remote01" /></p>
<p>Next, under ‘Other Users’, click ‘Add someone else to this PC’.</p>
<p><img src="../assets/images/2020/03/images/remote-2.png" alt="remote-2" /></p>
<p>Then select ‘I don’t have this persons sign-in information’</p>
<p><img src="../assets/images/2020/03/images/remote03.png" alt="remote03" /> I know, the UX is pretty weird here, because it’s shuttling you into setting up a Microsoft account, which you don’t need for this guide.[/caption]</p>
<p>Finally, provide a user name and a password, if you’d like, for your new user.</p>
<p><img src="../assets/images/2020/03/images/remote04.png" alt="remote04" /></p>
<blockquote>
<p>We’re doing this to make sure your child isn’t able to install programs or ‘mess up’ the computer. When they want to install an app, they’ll have to come to you for the admin password (hint: this is the account you used to login and make this account for them).</p>
</blockquote>
<p>Now, before you logoff, it’s time to setup the Remote Management Client.</p>
<h3 id="installing-tightvnc">Installing TightVNC</h3>
<p>We’re going to do this with the awesome and free TightVNC, <a href="../assets/images/2020/03/https://www.tightvnc.com/download.html">it’s available here.</a></p>
<p><a href="../assets/images/2020/03/https://www.tightvnc.com/download/2.8.27/tightvnc-2.8.27-gpl-setup-64bit.msi">Direct Download</a></p>
<p>On the computer we want to control, we’ll going to launch the installer we just downloaded and then click <strong>Next, Accept the Terms of the Agreement and Next.</strong></p>
<p>This will bring us to image three below, where we choose <strong>Custom Install</strong> .</p>
<p><img src="../assets/images/2020/03/images/remote05.png" alt="remote05" /> Next, Next, Custom, Next, Disable Viewer, Next Finish[/caption]</p>
<p>Now, on screen four, <strong>right-click ‘TightVNC Viewer’ and then choose ‘This Feature will not be available’, then click Next.</strong></p>
<p>Next, on screen 5, make SURE to check ‘Register TightVNC Server as a System Service’ is selected.</p>
<p><img src="../assets/images/2020/03/images/remote06.png" alt="remote06" /> Configuring ‘Register TightVNC Server as System Service’ makes the remoting app launch when Windows boots, no matter who is using it.[/caption]</p>
<p>And then click Next until at the below screen ‘<strong>TightVNC Server : Set Passwords”</strong></p>
<p><img src="../assets/images/2020/03/images/remote08.png" alt="remote08" /> Do not leave a remote client open with no password. Seriously, don’t do it.[/caption]</p>
<p><strong>It is critically important to use a password. This password is required any time you want to remote in to your child’s PC.</strong> Create a password and keep it safe, this is very important.</p>
<p>You should also password protect the Administrative Interface too, possibly with a different password.</p>
<h3 id="last-step---note-the-computer-name">Last step - note the computer name!</h3>
<p>You will need the computer name to connect to this PC. You can get it, or set it, by hitting ‘WindowsKey+X -> System Management’</p>
<p><img src="../assets/images/2020/03/images/remote-pcname.png" alt="remote-pcname" /></p>
<p>That’s it, now you can remote into this PC from another computer on the network!</p>
<p>AND, because your child will be running as a standard user account, this means that they cannot uninstall the app either, and also need your consent (and for you to login) to install new apps, great for keeping the PC pristine. Running as a Standard User mitigates the vast majority of hacks, and Cryptolocker too!</p>
<h3 id="setting-up-remote-viewer"> Setting up Remote Viewer</h3>
<p>On the computer you are going to connect from - run the same installer as above. However, this time when you get to screen 4, right-click ‘TightVNC Server’ and choose ‘this feature will not be installed’.</p>
<p>The PC you’re connecting from doesn’t need and shouldn’t have TightVNC Server running on it.</p>
<p><img src="../assets/images/2020/03/images/remote05.png" alt="remote05" /> This time, on image 4, disable TightVNC Server, and only install Viewer[/caption]</p>
<p>To connect, hit Start -> TightVNC Viewer. Then type in the name of your kid’s computer and hit Connect. Bam, it’s that easy.</p>
<p><img src="../assets/images/2020/03/images/remote-tightvncui.png" alt="remote-tightVNCUI" /></p>
<p>This setup has worked great for me to be able to quickly pop in and reopen the Scholastic Learn from Home courses and other materials when my kids accidentally close it every three minutes.</p>
<p>Where to go from here? I’m thinking of adding some additional guides, like how to prohibit certain apps from running, or keeping a Chrome browser window open</p>Stephen OwenWith everyone working remote now, it's really helpful to have a method to remote control your kid's computers, _especially_ if they are hard to keep on task like mine. So I wrote this short guide to help you get a handle. **This guide expects you to have two computers, one for you, one for your kids to use.**