Jekyll2023-10-16T09:18:38+00:00https://lachlanbarclay.net/feed.xmlLachlan (Rocklan) Barclay’s BlogLachlan Barclay (Rocklan's) Technical BlogLachlan BarclayJailbreaking a Japanese wii2023-10-16T00:00:00+00:002023-10-16T00:00:00+00:00https://lachlanbarclay.net/2023/10/Modding%20a%20Japanese%20Wii<p>So modding this Japanese wii has been an adventure.</p>
<p>Can’t use the normal exploit that I’ve used in the past which is named “LetterBomb”, because you need to know the MAC address of the wii, and I can’t open the settings menu on the wii. Not sure exactly why but I am guessing someone modded it in the past and stuffed it up
Can’t use the “str2hax” exploit because you have to change the DNS server, and I can’t get into settings</p>
<p>I then tried using “BlueBomb”, which exploits a vulnerability in the bluetooth driver, and you can only run it from linux, it would connect via bluetooth but the exploit would hang. No dice.</p>
<p>The only exploit left to run is an “old school” saved game exploit. You load a hacked saved game onto an SD card, copy it to your wii, run the game and load the saved game. They exist for a few games, none of which I own.</p>
<p>One of the games is “Lego Batman”, someone in Bayswater is selling it for $5. I buy it. Take it home, runs on the wii fine. Great. Copy the hacked save came onto an SD card, put it into the wii… and the wii doesn’t recognise my SD card. It doesn’t want to read ANY of my (5) SD cards. I find out it needs to be an original “SD Card”, not a “sdxc” or a “sdhc” card.</p>
<p>Find out a friend has an ancient SD card that should work. Meet up with him, take it home, it doesn’t work.</p>
<p>Find out my friend wiped it on a linux machine and didn’t create a partition on it. Boot linux, re-format SD card, create a partition, I can read it!</p>
<p>Copy hacked saved game onto SD card, put into wii, it appears! Copied save game file onto the wii.</p>
<p>Run Batman Lego. Saved game doesn’t appear.</p>
<p>After MUCH faffing and different attempts, it appears you need to do it in the following specific order:
Play the first stage of lego batman (I’ve done it 3 times now) and compete it until it creates a saved game on the wii
Copy this save game file onto your SD card (this sort of ‘initialises’ the SD card, not sure what it does)</p>
<p>Delete the save game file off the wii</p>
<p>Copy the hacked saved game onto the SD card</p>
<p>Copy the hacked saved game file from the SD card onto the wii</p>
<p>The save game now appears inside Lego Batman!!</p>
<p>Load the save game, do the exploit, and the exploit started up! Hooray! Here’s how it looks:</p>
<div class="embed-container"><iframe src="https://www.youtube.com/embed/2ggyx7_5wPc" frameborder="0" allowfullscreen=""></iframe></div>
<p>But oh no! The Exploit then hung! It turns out it doesn’t work on wii’s that haven’t been upgraded to the latest firmware!</p>
<p>So that’s where I am right now. Apparently I can use a different program to upgrade the firmware to the latest version but there’s a danger in bricking the wii. Oh well, I’m going to try it anyway.</p>
<p>Trying to fix the firmware installed on the wii manually, what could possibly go wrong</p>
<p><img src="/pics/wii/wii-fix-firmware.jpg" alt="Flashing firmware" class="img-responsive" /></p>
<p>Now let’s see if I’ve bricked it:</p>
<p><img src="/pics/wii/wii-maybe-bricked.jpg" alt="Maybe bricked?" class="img-responsive" /></p>
<p><img src="/pics/wii/hackmii.jpg" alt="Maybe bricked?" class="img-responsive" /></p>
<p><img src="/pics/wii/menu-version.jpg" alt="Menu version" class="img-responsive" /></p>Lachlan BarclaySo modding this Japanese wii has been an adventure.Inside my Yamaha DG-130 and Marshall 1936 cab2023-03-24T00:00:00+00:002023-03-24T00:00:00+00:00https://lachlanbarclay.net/2023/03/Amp-Insides<p><img src="/pics/amp/amp-1.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/amp-2.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/amp-3.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/amp-4.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/cab-1.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/cab-2.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/cab-3.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/cab-4.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>
<p><img src="/pics/amp/cab-5.jpg" alt="Yamaha dg 130" class="img-responsive" /></p>Lachlan BarclayAdding a circuit breaker to your ASP.NET application2023-01-03T00:00:00+00:002023-01-03T00:00:00+00:00https://lachlanbarclay.net/2023/01/Adding-A-Circuit-Breaker-To-Asp-Net-6-Application<p>If your application is making many calls to an API that periodically fails, it can be handy to set up some kind of “<a href="https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern" target="_blank">circuit breaker</a>” to stop your code from making unnecessary calls to an API that you know is down.</p>
<p>ASP.NET suggests using a library named <a href="https://www.thepollyproject.org/" target="_blank">Polly</a> for this, and it’s fantastic, but it’s also not exactly clear how to implement it properly… especially when you need <strong>multiple</strong> circuit breakers. So let’s do it.</p>
<h2 id="using-polly-in-your-aspnet-6-application">Using Polly in your ASP.NET 6 application</h2>
<p>In this example we have an application that calls two API’s - one is the <a href="https://developers.google.com/maps/documentation/timezone/overview">google timezone API</a>, which is called by using a standard <code class="language-plaintext highlighter-rouge">HttpClient</code>, and another is Redis, which uses it’s own <a href="https://github.com/StackExchange/StackExchange.Redis/">custom client</a>.</p>
<p>As a first step, create a few policies and add them to a <a href="https://github.com/App-vNext/Polly/wiki/PolicyRegistry" target="_blank">PolicyRegistry</a>. This is done so that each individual class can specify which policy it wishes to use.</p>
<p>In your <code class="language-plaintext highlighter-rouge">Program.cs</code> file:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">WebApplicationBuilder</span> <span class="n">builder</span> <span class="p">=</span> <span class="n">WebApplication</span><span class="p">.</span><span class="nf">CreateBuilder</span><span class="p">(</span><span class="n">args</span><span class="p">);</span>
<span class="c1">// I'm using Serilog, if you are using something else, you can add it here</span>
<span class="n">PollyPolicies</span> <span class="n">circuitBreakerManager</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">PollyPolicies</span><span class="p">(</span><span class="n">Serilog</span><span class="p">.</span><span class="n">Log</span><span class="p">.</span><span class="n">Logger</span><span class="p">);</span>
<span class="c1">// The creation of the policies is inside the PollyPolicies class. This is done</span>
<span class="c1">// just so that they are grouped together in one place alongside the logging</span>
<span class="kt">var</span> <span class="n">googleApiCircuitBreakerPolicy</span> <span class="p">=</span> <span class="n">circuitBreakerManager</span><span class="p">.</span><span class="nf">GetGoogleApiCircuitBreakerPolicy</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">redisCircuitBreakerPolicy</span> <span class="p">=</span> <span class="n">circuitBreakerManager</span><span class="p">.</span><span class="nf">GetRedisCircuitBreakerPolicy</span><span class="p">();</span>
<span class="c1">// We add our policies to a Registry which is just a glorified List<>. </span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddPolicyRegistry</span><span class="p">(</span><span class="k">new</span> <span class="nf">PolicyRegistry</span><span class="p">()</span>
<span class="p">{</span>
<span class="p">{</span> <span class="n">PollyPolicies</span><span class="p">.</span><span class="n">GooglePolicyName</span><span class="p">,</span> <span class="n">googleApiCircuitBreakerPolicy</span> <span class="p">},</span>
<span class="p">{</span> <span class="n">PollyPolicies</span><span class="p">.</span><span class="n">RedisPolicyName</span><span class="p">,</span> <span class="n">redisCircuitBreakerPolicy</span> <span class="p">}</span>
<span class="p">});</span>
<span class="c1">// When we setup our HttpClient, we need to also add the "addPolicyHandler" call as shown:</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddHttpClient</span><span class="p"><</span><span class="n">IGoogleTimezoneService</span><span class="p">,</span> <span class="n">GoogleTimezoneService</span><span class="p">>()</span>
<span class="p">.</span><span class="nf">AddPolicyHandler</span><span class="p">(</span><span class="n">googleApiCircuitBreakerPolicy</span><span class="p">);</span>
<span class="p">...</span>
<span class="n">app</span><span class="p">.</span><span class="nf">Run</span><span class="p">();</span>
</code></pre></div></div>
<p>Now we need to create our Policies, so create a file named <code class="language-plaintext highlighter-rouge">PollyPolicies.cs</code>:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PollyPolicies</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">GooglePolicyName</span> <span class="p">=</span> <span class="s">"googleApiCircuitBreaker"</span><span class="p">;</span>
<span class="k">public</span> <span class="k">const</span> <span class="kt">string</span> <span class="n">RedisPolicyName</span> <span class="p">=</span> <span class="s">"redisCircuitBreaker"</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">Serilog</span><span class="p">.</span><span class="n">ILogger</span> <span class="n">_logger</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">PollyPolicies</span><span class="p">(</span><span class="n">Serilog</span><span class="p">.</span><span class="n">ILogger</span> <span class="n">logger</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="n">IAsyncPolicy</span><span class="p"><</span><span class="n">HttpResponseMessage</span><span class="p">></span> <span class="nf">GetGoogleApiCircuitBreakerPolicy</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// The google API is called using a HttpClient, so we can use the handy</span>
<span class="c1">// HandleTransientHttpError() call. But this will not catch timeouts!</span>
<span class="c1">// Make sure to fine-tune these numbers for your specific use-case</span>
<span class="k">return</span> <span class="n">HttpPolicyExtensions</span>
<span class="p">.</span><span class="nf">HandleTransientHttpError</span><span class="p">()</span>
<span class="p">.</span><span class="nf">AdvancedCircuitBreakerAsync</span><span class="p">(</span>
<span class="n">failureThreshold</span><span class="p">:</span> <span class="m">0.1</span><span class="p">,</span>
<span class="n">samplingDuration</span><span class="p">:</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">20</span><span class="p">),</span>
<span class="n">minimumThroughput</span><span class="p">:</span> <span class="m">5</span><span class="p">,</span>
<span class="n">durationOfBreak</span><span class="p">:</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">60</span><span class="p">),</span>
<span class="k">this</span><span class="p">.</span><span class="n">OnGoogleBreak</span><span class="p">,</span>
<span class="k">this</span><span class="p">.</span><span class="n">OnGoogleReset</span><span class="p">,</span>
<span class="k">this</span><span class="p">.</span><span class="n">OnGoogleHalfOpen</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="n">AsyncCircuitBreakerPolicy</span> <span class="nf">GetRedisCircuitBreakerPolicy</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// The Redis API throws quite a few different exceptions (about 5) depending</span>
<span class="c1">// on what the problem is, so we're just going to handle any exception:</span>
<span class="k">return</span> <span class="n">Policy</span><span class="p">.</span><span class="n">Handle</span><span class="p"><</span><span class="n">Exception</span><span class="p">>().</span><span class="nf">AdvancedCircuitBreakerAsync</span><span class="p">(</span>
<span class="n">failureThreshold</span><span class="p">:</span> <span class="m">0.01</span><span class="p">,</span>
<span class="n">samplingDuration</span><span class="p">:</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">5</span><span class="p">),</span>
<span class="n">minimumThroughput</span><span class="p">:</span> <span class="m">15</span><span class="p">,</span>
<span class="n">durationOfBreak</span><span class="p">:</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">360</span><span class="p">),</span>
<span class="k">this</span><span class="p">.</span><span class="n">OnRedisBreak</span><span class="p">,</span>
<span class="k">this</span><span class="p">.</span><span class="n">OnRedisReset</span><span class="p">,</span>
<span class="k">this</span><span class="p">.</span><span class="n">OnRedisHalfOpen</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnGoogleReset</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">?.</span><span class="nf">Warning</span><span class="p">(</span><span class="s">"Google API circuit closed, requests flow normally."</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnGoogleHalfOpen</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">?.</span><span class="nf">Warning</span><span class="p">(</span><span class="s">"Google API circuit half open"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnGoogleBreak</span><span class="p">(</span><span class="n">DelegateResult</span><span class="p"><</span><span class="n">HttpResponseMessage</span><span class="p">></span> <span class="n">result</span><span class="p">,</span> <span class="n">TimeSpan</span> <span class="n">ts</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">?.</span><span class="nf">Warning</span><span class="p">(</span><span class="s">"Google API circuit cut because {ResultStatusCode} "</span> <span class="p">+</span>
<span class="s">"or {Exception}, so requests will not flow."</span><span class="p">,</span>
<span class="n">result</span><span class="p">.</span><span class="n">Exception</span><span class="p">,</span> <span class="n">result</span><span class="p">.</span><span class="n">Result</span><span class="p">?.</span><span class="n">StatusCode</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnRedisReset</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">?.</span><span class="nf">Warning</span><span class="p">(</span><span class="s">"Redis circuit closed, requests flow normally."</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnRedisHalfOpen</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">?.</span><span class="nf">Warning</span><span class="p">(</span><span class="s">"Redis circuit half open"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">OnRedisBreak</span><span class="p">(</span><span class="n">Exception</span> <span class="n">result</span><span class="p">,</span> <span class="n">TimeSpan</span> <span class="n">ts</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">?.</span><span class="nf">Warning</span><span class="p">(</span><span class="s">"Redis circuit cut because {Exception}, "</span> <span class="p">+</span>
<span class="s">"so requests will not flow."</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We’ve now added two different circuit breaker policies that we can use in our code wherever we want. Let’s go ahead and use this policy in some of our code.</p>
<h2 id="using-the-circuit-breaker">Using the circuit breaker</h2>
<p>Now we need to update our code that calls the google API to use our circuit breaker. The cool part of the previous call to <code class="language-plaintext highlighter-rouge">AddPolicyHandler()</code> that is chained to the <code class="language-plaintext highlighter-rouge">AddHttpClient</code> call means that it will be used automatically! I’m not sure if this is cool or a bit too magical, but that’s what you need to do.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">partial</span> <span class="k">class</span> <span class="nc">GoogleTimezoneService</span> <span class="p">:</span> <span class="n">IGoogleTimezoneService</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">HttpClient</span> <span class="n">_httpClient</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p"><</span><span class="n">GoogleTimezoneService</span><span class="p">></span> <span class="n">_logger</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">ICircuitBreakerPolicy</span><span class="p"><</span><span class="n">HttpResponseMessage</span><span class="p">></span> <span class="n">_breaker</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">GoogleTimezoneService</span><span class="p">(</span>
<span class="n">HttpClient</span> <span class="n">httpClient</span><span class="p">,</span>
<span class="n">ILogger</span><span class="p"><</span><span class="n">GoogleTimezoneService</span><span class="p">></span> <span class="n">logger</span><span class="p">,</span>
<span class="n">IReadOnlyPolicyRegistry</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="n">pollyPolicyRegistry</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span><span class="p">;</span>
<span class="n">_httpClient</span> <span class="p">=</span> <span class="n">httpClient</span><span class="p">;</span>
<span class="n">_breaker</span> <span class="p">=</span> <span class="n">pollyPolicyRegistry</span><span class="p">.</span><span class="n">Get</span><span class="p"><</span><span class="n">ICircuitBreakerPolicy</span><span class="p"><</span><span class="n">HttpResponseMessage</span><span class="p">>>(</span>
<span class="n">PollyPolicyNames</span><span class="p">.</span><span class="n">GooglePolicyName</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">GetResponseContentAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">endpoint</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="c1">// This is a bit of optimisation. When the circuit breaker is closed, it </span>
<span class="c1">// will throw a BrokenCircuitException if we try to use it. However if we </span>
<span class="c1">// know it's closed, there's no point in making the request and throwing</span>
<span class="c1">// the exception, so let's just return</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_breaker</span><span class="p">.</span><span class="n">CircuitState</span> <span class="p">==</span> <span class="n">CircuitState</span><span class="p">.</span><span class="n">Open</span> <span class="p">||</span>
<span class="n">_breaker</span><span class="p">.</span><span class="n">CircuitState</span> <span class="p">==</span> <span class="n">CircuitState</span><span class="p">.</span><span class="n">Isolated</span><span class="p">)</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="n">HttpResponseMessage</span> <span class="n">response</span> <span class="p">=</span> <span class="k">await</span> <span class="n">_httpClient</span><span class="p">.</span><span class="nf">GetAsync</span><span class="p">(</span><span class="n">endpoint</span><span class="p">);</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">response</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsStringAsync</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">BrokenCircuitException</span> <span class="n">ex</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// this can happen when the circuit has been set to half open, and </span>
<span class="c1">// we are verifying that it's back up, but it's not, so it throws a</span>
<span class="c1">// BrokenCircuitException, so in that case, we might as well log it </span>
<span class="c1">// and just return null</span>
<span class="n">_logger</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"Exception {e} when trying to re-open circuit for "</span> <span class="p">+</span>
<span class="s">"google timezone api at {Url}"</span><span class="p">,</span>
<span class="n">ex</span><span class="p">,</span> <span class="n">_httpClient</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="p">+</span> <span class="n">endpoint</span><span class="p">);</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"Exception {e} when calling google timezone api at {Url}"</span><span class="p">,</span>
<span class="n">ex</span><span class="p">,</span> <span class="n">_httpClient</span><span class="p">.</span><span class="n">BaseAddress</span> <span class="p">+</span> <span class="n">endpoint</span><span class="p">);</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now we need to update our Redis code to use our circuit breaker. In this case we need to retrieve it from the polly registry and then use it. We are going to use the <code class="language-plaintext highlighter-rouge">ExecuteAsync()</code> call, which executes any code you want, and behaves like how we defined it in the policy. In this case if the code throws any <code class="language-plaintext highlighter-rouge">Exception</code>, it will handle it according to our desired circuit breaker policy logic.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">RedisCache</span>
<span class="p">{</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p"><</span><span class="n">RedisCache</span><span class="p">></span> <span class="n">_logger</span><span class="p">;</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">AsyncCircuitBreakerPolicy</span> <span class="n">_circuitBreakerPolicy</span><span class="p">;</span>
<span class="k">public</span> <span class="nf">RedisCache</span><span class="p">(</span><span class="n">ILogger</span><span class="p"><</span><span class="n">RedisCache</span><span class="p">></span> <span class="n">logger</span><span class="p">,</span>
<span class="n">IReadOnlyPolicyRegistry</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="n">pollyPolicyRegistry</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span><span class="p">;</span>
<span class="n">_circuitBreakerPolicy</span> <span class="p">=</span> <span class="n">pollyPolicyRegistry</span><span class="p">.</span><span class="n">Get</span><span class="p"><</span><span class="n">AsyncCircuitBreakerPolicy</span><span class="p">>(</span>
<span class="n">PollyPolicyNames</span><span class="p">.</span><span class="n">RedisPolicyName</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="c1">// returns a string from redis</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="kt">string</span><span class="p">></span> <span class="nf">GetStringAsync</span><span class="p">(</span><span class="kt">string</span> <span class="n">key</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// this stops us from making the call if the circuit is open. If we take</span>
<span class="c1">// this out, the ExecuteAsync() method below will throw a BrokenCircuitException</span>
<span class="c1">// on every call, so this is just a performance optimisation:</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_circuitBreakerPolicy</span><span class="p">.</span><span class="n">CircuitState</span> <span class="p">==</span> <span class="n">CircuitState</span><span class="p">.</span><span class="n">Open</span> <span class="p">||</span>
<span class="n">_circuitBreakerPolicy</span><span class="p">.</span><span class="n">CircuitState</span> <span class="p">==</span> <span class="n">CircuitState</span><span class="p">.</span><span class="n">Isolated</span><span class="p">)</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="c1">// Where the magic happens. ExecuteAsync will handle and then re-raise any </span>
<span class="c1">// exceptions thrown, so we need to catch them here too.</span>
<span class="k">try</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="n">_circuitBreakerPolicy</span><span class="p">.</span><span class="nf">ExecuteAsync</span><span class="p">(()</span> <span class="p">=></span>
<span class="n">_redisClient</span><span class="p">.</span><span class="nf">GetStringAsync</span><span class="p">(</span><span class="n">key</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_logger</span><span class="p">.</span><span class="nf">LogWarning</span><span class="p">(</span><span class="s">"Error with Redis when fetching {key}: {e}"</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And that’s the bare minimum that you need to be able to enforce a circuit breaker for any code that might throw an exception.</p>
<h2 id="adding-extra-policies">Adding extra policies</h2>
<p>If you would also like to use other policies like automatic retries, you can add them at startup:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">retryPolicy</span> <span class="p">=</span> <span class="n">HttpPolicyExtensions</span>
<span class="p">.</span><span class="nf">HandleTransientHttpError</span><span class="p">()</span>
<span class="p">.</span><span class="nf">WaitAndRetryAsync</span><span class="p">(</span><span class="m">3</span><span class="p">,</span> <span class="n">retryNumber</span> <span class="p">=></span> <span class="n">TimeSpan</span><span class="p">.</span><span class="nf">FromSeconds</span><span class="p">(</span><span class="m">1</span><span class="p">));</span>
</code></pre></div></div>
<p>and you can chain policies together:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddHttpClient</span><span class="p"><</span><span class="n">IGoogleTimezoneService</span><span class="p">,</span> <span class="n">GoogleTimezoneService</span><span class="p">>()</span>
<span class="p">.</span><span class="nf">AddPolicyHandler</span><span class="p">(</span><span class="n">googleApiRetryPolicy</span><span class="p">)</span>
<span class="p">.</span><span class="nf">AddPolicyHandler</span><span class="p">(</span><span class="n">googleApiCircuitBreakerPolicy</span><span class="p">);</span>
</code></pre></div></div>
<p>That should be enough to get you started. Now make sure you test your circuit breaker code! It’s easy to get wrong!</p>Lachlan BarclayIn this example we have an application that calls two API's - one is the google timezone API, which is called by using a standard HttpClient, and another is Redis, which uses it's own custom client. As a first step, we need to create a few policies and add them to a PolicyRegistry.Mounting a bike rack to your brick wall2023-01-03T00:00:00+00:002023-01-03T00:00:00+00:00https://lachlanbarclay.net/2023/01/Mounting-Bike-Rack-To-Brick<p>Let’s talk about how to mount a bike rack to a brick wall. I’m using a <a href="https://www.bunnings.com.au/handy-storage-wall-mounted-bike-and-helmet-hook_p0074299">Handy Storage Bike Rack</a>:</p>
<p><img src="/pics/bike/bike-rack-1.jpg" alt="Handy Storage Bike Rack" class="img-responsive" /></p>
<p>The first step is to throw out the useless wall plug and screw that it came with:</p>
<p><img src="/pics/bike/bike-rack-2.jpg" alt="Useless wall plugs" class="img-responsive" /></p>
<p>And instead, buy some <a href="https://www.bunnings.com.au/ramset-8-x-40mm-dynabolt-plus-hex-nut-bolt_p2269155">8x40mm dynabolts</a>:</p>
<p><img src="/pics/bike/bike-rack-9.jpg" alt="Dynabolt Plus 8mm by 40mm" class="img-responsive" /></p>
<p>Next step, remove the two allen key bolts, as they get in the way:</p>
<p><img src="/pics/bike/bike-rack-3.jpg" alt="Bike Rack Bracket" class="img-responsive" /></p>
<p>You’ll end up with the bracket:</p>
<p><img src="/pics/bike/bike-rack-4.jpg" alt="Bike Rack Bracket without hooks" class="img-responsive" /></p>
<p>Put it up against the wall and draw a dot inside each of the three holes:</p>
<p><img src="/pics/bike/bike-rack-5.jpg" alt="Bike Rack Bracket against brick wall" class="img-responsive" /></p>
<p>You should end up with something like this:</p>
<p><img src="/pics/bike/bike-rack-7.jpg" alt="Brick wall sharpie marks" class="img-responsive" /></p>
<p>And the next step is to drill out one of those holes. You’ll need a some <a href="https://www.kangotools.com.au/products/details/10-piece-masonry-drill-bit-set-kmdb10s2">masonry drill bits</a>. You’ll also need a drill with a hammer action - I suggest getting a corded drill if you only have a battery operated one, as they have a lot more power.</p>
<p>You’ll also need to work out how far to drill in. The dynabolt needs to go in as far as the bolt on the end:</p>
<p><img src="/pics/bike/bike-rack-10.jpg" alt="Drill bit against dynabolt" class="img-responsive" /></p>
<p>So to make sure you don’t drill in too far, line up the drill bit and the dynabolt, and you can either use a bit of tape around the drill bit to make sure you don’t go in too far, or in my case the drill that I’m using has a depth gauge:</p>
<p><img src="/pics/bike/bike-rack-11.jpg" alt="Drill depth gauge and dynabolt" class="img-responsive" /></p>
<p>Now get yourself some protection. Ears, eyes, mouth and nose:</p>
<p><img src="/pics/bike/bike-rack-8.jpg" alt="Ear, eyes, nose protection" class="img-responsive" /></p>
<p>Start with the smallest drill bit size (5mm) and get drilling. In my case I started with the bottom hole. Hopefully you’ll end up with a hole like this:</p>
<p><img src="/pics/bike/bike-rack-12.jpg" alt="Brick wall small drilled hole" class="img-responsive" /></p>
<p>Now this is the crucial bit! Put the bracket over the hole that you just drilled and make sure things still line up:</p>
<p><img src="/pics/bike/bike-rack-13.jpg" alt="Bracket against brick wall" class="img-responsive" /></p>
<p>If not, get out that sharpie again and create a new mark. Drill the top hole next, and then make sure the bracket holes still line up. In this case you can see that the middle hole was a tiny bit too high:</p>
<p><img src="/pics/bike/bike-rack-14.jpg" alt="Bracket against brick wall holes" class="img-responsive" /></p>
<p>Now that we have three 5mm holes drilled, we need to expand them to fit the dynabolt. In this case, the 8mm dynabolt needs an 8mm drill bit:</p>
<p><img src="/pics/bike/bike-rack-15.jpg" alt="8mm Drill bit and dynabolt" class="img-responsive" /></p>
<p>Let’s line them up to make sure. As you can see here, the bolt sticks out from behind the drill bit, and the drill bit is the same width as the sleeve bolt itself:</p>
<p><img src="/pics/bike/bike-rack-16.jpg" alt="Dynabolt and drill bit aligned" class="img-responsive" /></p>
<p>Now that you’ve drilled the 8mm holes, take the bolts off the dynabolts:</p>
<p><img src="/pics/bike/bike-rack-17.jpg" alt="Detached dynabolts" class="img-responsive" /></p>
<p>And attach them to the bracket. You only want to screw them on a little bit as shown here:</p>
<p><img src="/pics/bike/bike-rack-18.jpg" alt="Dynabolts attached to bike rack bracket" class="img-responsive" /></p>
<p>Now line them up with your holes:</p>
<p><img src="/pics/bike/bike-rack-19.jpg" alt="Bike Rack Bracket ready to attach to wall" class="img-responsive" /></p>
<p>And with a good old fashioned hammer, smack them in. Do each one 1 or 2cm at a time. Make sure to follow through with your hammer strokes! You should end up with the bracket against the bricks. Don’t stress if it’s not flush against the bricks. Now for the fun part. Using a wrench, tighten up those bolts. Go crazy!</p>
<p><img src="/pics/bike/bike-rack-22.jpg" alt="Bike Rack Bracket attached to wall" class="img-responsive" /></p>
<p>Now re-attach the bike rack to the bracket using the allen key bolts, and voila, one bike rack!</p>
<p><img src="/pics/bike/bike-rack-23.jpg" alt="Bike Rack attached to wall with bikes" class="img-responsive" /></p>Lachlan BarclayMounting a bike rack to a brick wall ain't easy. Here's how I did it.Programming an Arduino to connect a dance pad via USB for StepMania2022-11-16T00:00:00+00:002022-11-16T00:00:00+00:00https://lachlanbarclay.net/2022/11/StepMania-DDR-Arduino-Dance-Pad-Controller<p>So for the past few months I’ve been getting into DDR on the Wii. It’s great fun and I’m getting pretty good, and what I really need now is to be able to slow down the game to practice some of the faster, trickier sections. But… the game doesn’t support that.</p>
<p>Fortunately <a href="https://www.stepmania.com/">StepMania</a> does, but it only runs on a PC, so to be able to play it there, I need a dance pad that I can plug in via USB.</p>
<p>Unfortunately in Australia they are crazy expensive. <strong>The</strong> one to get is the <a href="https://www.maty-taneczne.pl/shop/dance-mat-ltek-ex-pro-2/">L-TEK hard pad</a> from Poland. However that will cost me like $550.</p>
<p>Luckily, I found the <a href="https://www.ddrgame.com/dance-dance-revolution-pc-energy-metal-stepmania.html">DDR A Game</a> pad on marketplace:</p>
<p><img src="/images/ddr-pad-4.jpg" alt="Stepmania dance pad" class="img-responsive" /></p>
<p>While it looks cool - these particular pads don’t have the best reviews. Apparently they aren’t made particularly well.. but hey, how hard can it be to improve? I also had no idea if it worked, but really… it’s just a bunch of buttons. So I bought it.</p>
<p>The connector at the top of the pad is a “VGA” port (technically a <a href="https://en.wikipedia.org/wiki/D-subminiature">DE-15 d-sub</a>, and it seems like you can buy different connectors for it so that you can plug your pad into a PC, or a PS-2, or a wii, etc. So yeah, I could just <a href="https://www.ddrgame.com/dance-dance-revolution-control-box-4-in-1-blue.html">buy the right one online</a>, but where’s the fun in that? Maybe I can build one myself using an Arduino and have lots of fun in the process?</p>
<p>After a bit of poking around the VGA connector with a multimeter, I found that the middle pin is the ground, and the pads are just simple switches hooked up to individual pins. So all I needed was to connect it up to an Arduino and make it simulate a keyboard.</p>
<p>Here’s my first attempt, I bought a male VGA plug, soldered up the connectors and plugged them into an Arduino Uno:</p>
<p><img src="/images/ddr-pad-1.jpg" alt="Arduino Uno dance pad controller" class="img-responsive" /></p>
<p>And then I programmed it to light up the onboard LED whenever a button is pressed:</p>
<div class="embed-container"><iframe src="https://www.youtube.com/embed/cRqkQR6YpxA" frameborder="0" allowfullscreen=""></iframe></div>
<p>Woo! All the pads/buttons seem to work ok!</p>
<p>All I need now is to emulate a USB keyboard from the Arduino… but it turns out that an Arduino Uno can’t emulate a USB device, what I needed was an <a href="https://docs.arduino.cc/hardware/leonardo">Arduino Leonardo</a>. So I bought one of them, wired it up:</p>
<p><img src="/images/ddr-leonardo.jpg" alt="Arduino Leonardo dance pad controller" class="img-responsive" /></p>
<p>And wrote some code to emulate keyboard presses:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include "Keyboard.h"
</span>
<span class="c1">// the number of the onboard LED pin</span>
<span class="k">const</span> <span class="kt">int</span> <span class="n">ledPin</span> <span class="o">=</span> <span class="mi">13</span><span class="p">;</span>
<span class="c1">// these are the inputs on the arduino that we expect the pads to be wired into</span>
<span class="kt">int</span> <span class="n">buttonPin</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">12</span><span class="p">};</span>
<span class="c1">// current button states</span>
<span class="kt">int</span> <span class="n">buttonState</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">};</span>
<span class="c1">// previous button states</span>
<span class="kt">int</span> <span class="n">prevButtonState</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">};</span>
<span class="c1">// these are the keys that are simulated by the keypad</span>
<span class="kt">int</span> <span class="n">keyboardButtons</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="n">KEY_RIGHT_ARROW</span><span class="p">,</span> <span class="n">KEY_DOWN_ARROW</span><span class="p">,</span> <span class="n">KEY_UP_ARROW</span><span class="p">,</span> <span class="n">KEY_PAGE_DOWN</span><span class="p">,</span> <span class="n">KEY_PAGE_UP</span><span class="p">,</span> <span class="n">KEY_LEFT_ARROW</span><span class="p">};</span>
<span class="c1">// flag indicating that at least one button is pressed, if true, we light up the onboard LED</span>
<span class="kt">bool</span> <span class="n">buttonPressed</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// initialize the onboard LED pin as an output </span>
<span class="n">pinMode</span><span class="p">(</span><span class="n">ledPin</span><span class="p">,</span> <span class="n">OUTPUT</span><span class="p">);</span>
<span class="c1">// initialize the 6 inputs as pullup inputs</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="mi">6</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">pinMode</span><span class="p">(</span><span class="n">buttonPin</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">INPUT_PULLUP</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Initialise keyboard</span>
<span class="n">Keyboard</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
<span class="n">buttonPressed</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="c1">// read the state of the input buttons</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="mi">6</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">digitalRead</span><span class="p">(</span><span class="n">buttonPin</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">ledPin</span><span class="p">,</span> <span class="n">HIGH</span><span class="p">);</span> <span class="c1">// turn led on</span>
<span class="n">buttonPressed</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// If a button has changed state and it's pressed</span>
<span class="k">if</span> <span class="p">(</span><span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="n">prevButtonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&&</span> <span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="n">LOW</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Keyboard</span><span class="p">.</span><span class="n">press</span><span class="p">(</span><span class="n">keyboardButtons</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="n">prevButtonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
<span class="c1">// If a button has changed state and it's released</span>
<span class="k">if</span> <span class="p">(</span><span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="n">prevButtonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&&</span> <span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="n">HIGH</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Keyboard</span><span class="p">.</span><span class="n">release</span><span class="p">(</span><span class="n">keyboardButtons</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="n">prevButtonState</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">buttonState</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// If none of the buttons are pressed, turn off the LED</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">buttonPressed</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">digitalWrite</span><span class="p">(</span><span class="n">ledPin</span><span class="p">,</span> <span class="n">LOW</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And it worked!</p>
<div class="embed-container"><iframe src="https://www.youtube.com/embed/IZYFiDaa7q4" frameborder="0" allowfullscreen=""></iframe></div>
<p>Here’s a link to the code in github if anyone needs it: <a href="https://github.com/rocklan/ddr-dance-pad">https://github.com/rocklan/ddr-dance-pad</a>.</p>
<p>So what’s the moral of the story? Building stuff yourself is fun. Don’t be afraid to try!</p>Lachlan BarclayProgramming an Arduino based USB controller for a DDR/StepMania dance pad. How hard can it be?Song ‘You are a star’ chord chart2022-08-07T00:00:00+00:002022-08-07T00:00:00+00:00https://lachlanbarclay.net/2022/08/You-Are-A-Star<p>I’ve started playing “Hottest Dance Party 3” on the wii. It’s a bit of fun. Quite impressed with this track, the chord progressions are great and the instrumentation is very cool.</p>
<div class="embed-container"><iframe src="https://www.youtube.com/embed/yAxrz2ZMkvU" frameborder="0" allowfullscreen=""></iframe></div>
<p>Here’s the chart:</p>
<pre>
<b>Verse 1</b>
Bm
It's 5AM, now it's time to get out of bed
Another day is ready to start again
G
The shower's running, The coffee is burning now
E A G F#7
Gotta get out the door in time for the train
<b>Verse 2</b>
Bm
Cell phone is ringing, the message is coming in
The traffic's jammed, now let's see who is gonna win. yeah the
G A Bsus4 B7
game is on, if you are strong and you wanna play...
<b>Pre-Chorus</b>
Em F#/Bb
No one said the race would be just a piece of cake
F#m B7
Work your way to the top! You've got to
Em E
Put in the time pay your dues
A F#/Bb
The corner office is there, Waiting for you!
<b>Chorus:</b>
Emaj7 Eb7
You, yeah, you are a star there's no mistaking you will
Abmsus4 F#m B7
Make it to the end! I know you've
Emaj7 Eb7
got what it takes to believe in yourself
Abm F#m B7
You will never give in, you're a star!
Emaj7 F#
No one else in the world could replace the way you smile
Ebm F#m B7 Emaj7
The way you look when you're inspired
Ebm Abm
I know you will shine no matter what you will face
'Cause you're are a star!
<b>Outro</b>
Bm x6
F#m7 x2
Bm
</pre>Lachlan BarclayChord chart for "You are a star", which is a song featured in "Hottest Dance Party 3" wii game.Updating IIS certificates with powershell2022-01-10T00:00:00+00:002022-01-10T00:00:00+00:00https://lachlanbarclay.net/2022/01/Updating-IIS-Certificates-With-Powershell<p>Let’s say you were in a situation where you had to update a certificate that was installed on a lot of servers. Do you want to do it all manually? Heck no. Let’s hack together some powershell to make it work. This code is very loosely based on <a href="https://devblogs.microsoft.com/scripting/weekend-scripter-use-powershell-to-update-ssl-bindings/">this one</a> except the typos have been fixed and there’s no magic IP addresses :)</p>
<h3 id="importing-the-certificate-locally">Importing the certificate locally</h3>
<p>Let’s get it working locally first. Within a powershell window, let’s try a few commands. First off, let’s load in our new certificate that we want to install. Assuming that we have a certificate in <code class="language-plaintext highlighter-rouge">pfx</code> format that is ready to be imported, let’s install it into our certificate store:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$certname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"c:\my-certificate.pfx"</span><span class="w">
</span><span class="nv">$certpwd</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"certificate-install-password"</span><span class="w">
</span><span class="nv">$pfxpass</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$certpwd</span><span class="w"> </span><span class="o">|</span><span class="n">ConvertTo-SecureString</span><span class="w"> </span><span class="nt">-AsPlainText</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="nv">$newCert</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Import-PfxCertificate</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="nv">$certname</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-CertStoreLocation</span><span class="w"> </span><span class="s2">"Cert:\LocalMachine\My"</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-password</span><span class="w"> </span><span class="nv">$pfxpass</span><span class="w">
</span></code></pre></div></div>
<p>Your new certificate should be installed and viewiable in IIS by clicking on your computer name (not on one of the sites!) and then choosing “Server Certificates”, and you should see something like this:</p>
<p><img src="/images/cert-in-iis.png" alt="certificate showing in IIS" class="img-responsive" /></p>
<p>Great!</p>
<h3 id="updating-iis-to-use-the-new-certificate">Updating IIS to use the new certificate</h3>
<p>Now how about we update one particular binding to use this new certificate:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># load in the admin scripts for doing stuff with IIS:</span><span class="w">
</span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">Webadministration</span><span class="w">
</span><span class="c"># fetch the default web site:</span><span class="w">
</span><span class="nv">$site</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"IIS:\Sites"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">where</span><span class="w"> </span><span class="p">{(</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">Name</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">"Default Web Site"</span><span class="w"> </span><span class="p">)}</span><span class="w">
</span></code></pre></div></div>
<p>You might want to check that your <code class="language-plaintext highlighter-rouge">$site</code> variable has the right web site before you proceed. Just run <code class="language-plaintext highlighter-rouge">echo $site</code> and it should be pretty obvious.</p>
<p>Now let’s fetch the first SSL binding that is mapped to port 443:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">
</span><span class="nv">$binding</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$site</span><span class="o">.</span><span class="nf">Bindings</span><span class="o">.</span><span class="nf">Collection</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="err">`</span><span class="w">
</span><span class="n">where</span><span class="w"> </span><span class="p">{(</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">protocol</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'https'</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$_</span><span class="o">.</span><span class="nf">bindingInformation</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'*:443:'</span><span class="p">)}</span><span class="w">
</span></code></pre></div></div>
<p>and let’s do the magic:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">
</span><span class="nv">$binding</span><span class="o">.</span><span class="nf">AddSslCertificate</span><span class="p">(</span><span class="nv">$newCert</span><span class="o">.</span><span class="nf">Thumbprint</span><span class="p">,</span><span class="w"> </span><span class="s2">"my"</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>
<p>BTW, this <code class="language-plaintext highlighter-rouge">AddSslCertificate</code> command doesn’t seem to be documented anywhere, and in my opinion it’s confusingly named - if there’s already an SSL certificate assigned to the binding then it will <em>update</em> it to use the new one. But hey, it works:</p>
<p><img src="/images/cert-binding-updated.png" alt="certificate updated IIS binding" class="img-responsive" /></p>
<p>Woo!</p>
<p>Now this is all well and good, but how do we do this across multiple servers, remotely?</p>
<h2 id="installing-certs-on-multiple-servers">Installing certs on multiple servers</h2>
<p>Let’s copy the cert to a few servers:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$servers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="s1">'server1'</span><span class="p">,</span><span class="s1">'server2'</span><span class="p">,</span><span class="s1">'server3'</span><span class="p">)</span><span class="w">
</span><span class="nv">$servers</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">foreach-Object</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">copy-item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$certname</span><span class="w"> </span><span class="nt">-Destination</span><span class="w"> </span><span class="s2">"\\</span><span class="bp">$_</span><span class="s2">\c</span><span class="se">`$</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>This will copy the cert into the <code class="language-plaintext highlighter-rouge">c:\</code> drive on the desired servers. Note the extra confusing use of the tilde character which you have to use in powershell to escape a dollar sign in a string!</p>
<p>Now let’s connect to all of these servers and import the certificate:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Establish a connection to all of those servers:</span><span class="w">
</span><span class="nv">$session</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-PsSession</span><span class="w"> </span><span class="nt">-ComputerName</span><span class="w"> </span><span class="nv">$servers</span><span class="w">
</span><span class="nv">$importCertificatesCommand</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">({</span><span class="w">
</span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">Webadministration</span><span class="w">
</span><span class="nv">$newCert</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Import-PfxCertificate</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="s2">"c:\</span><span class="si">$(</span><span class="nv">$</span><span class="nn">using</span><span class="p">:</span><span class="nv">certname</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-CertStoreLocation</span><span class="w"> </span><span class="s2">"Cert:\LocalMachine\My"</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-password</span><span class="w"> </span><span class="nv">$</span><span class="nn">using</span><span class="p">:</span><span class="nv">pfxpass</span><span class="w">
</span><span class="p">})</span><span class="w">
</span><span class="n">Invoke-Command</span><span class="w"> </span><span class="nt">-session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="nt">-scriptblock</span><span class="w"> </span><span class="nv">$importCertificatesCommand</span><span class="w">
</span></code></pre></div></div>
<p>There’s a bunch of magic here. <code class="language-plaintext highlighter-rouge">New-PsSession</code> creates a new connection to a remote server, for the supplied list of server names. <code class="language-plaintext highlighter-rouge">Invoke-Command</code> executes the desired command against all of the servers that we’ve opened a connection to, and then we are adding the <code class="language-plaintext highlighter-rouge">$using</code> prefix to any variables that we wish to access outside of this command.</p>
<p>Once that’s done, we can delete the certificate off the servers:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Invoke-command</span><span class="w"> </span><span class="nt">-Session</span><span class="w"> </span><span class="nv">$session</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">remove-item</span><span class="w"> </span><span class="nt">-path</span><span class="w"> </span><span class="s2">"c:\</span><span class="si">$(</span><span class="nv">$</span><span class="nn">using</span><span class="p">:</span><span class="nv">certname</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Things are starting to look good! We have the cert imported, shall we update IIS to use this new certificate across all our servers, if they are using an older certificate that we know is about to expire? Let’s update our command:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$importCertificatesCommand</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">({</span><span class="w">
</span><span class="nv">$newCert</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Import-PfxCertificate</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="s2">"c:\</span><span class="si">$(</span><span class="nv">$</span><span class="nn">using</span><span class="p">:</span><span class="nv">certname</span><span class="si">)</span><span class="s2">"</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-CertStoreLocation</span><span class="w"> </span><span class="s2">"Cert:\LocalMachine\My"</span><span class="w"> </span><span class="se">`
</span><span class="w"> </span><span class="nt">-password</span><span class="w"> </span><span class="nv">$</span><span class="nn">using</span><span class="p">:</span><span class="nv">pfxpass</span><span class="w">
</span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">Webadministration</span><span class="w">
</span><span class="nv">$sites</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nx">IIS:\Sites</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$site</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$sites</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$binding</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$site</span><span class="o">.</span><span class="nf">Bindings</span><span class="o">.</span><span class="nf">Collection</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">$binding</span><span class="o">.</span><span class="nf">protocol</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'https'</span><span class="p">)</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nv">$search</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Cert:\LocalMachine\My\</span><span class="si">$(</span><span class="nv">$binding</span><span class="o">.</span><span class="nf">certificateHash</span><span class="si">)</span><span class="s2">"</span><span class="w">
</span><span class="nv">$certs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-path</span><span class="w"> </span><span class="nv">$search</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w">
</span><span class="nv">$hostname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">hostname</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="nv">$certs</span><span class="o">.</span><span class="nf">count</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w">
</span><span class="p">(</span><span class="nv">$certs</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">Subject</span><span class="o">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s2">"CN=MyOldCertificate"</span><span class="p">)))</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="n">echo</span><span class="w"> </span><span class="s2">"Updating </span><span class="nv">$hostname</span><span class="s2">, site: </span><span class="se">`"</span><span class="si">$(</span><span class="nv">$site</span><span class="o">.</span><span class="nf">name</span><span class="si">)</span><span class="se">`"</span><span class="s2">, binding: </span><span class="se">`"</span><span class="si">$(</span><span class="nv">$binding</span><span class="o">.</span><span class="nf">bindingInformation</span><span class="si">)</span><span class="se">`"</span><span class="s2">, current cert: </span><span class="se">`"</span><span class="si">$(</span><span class="nv">$certs</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">Subject</span><span class="si">)</span><span class="se">`"</span><span class="s2">, Expiry Date: </span><span class="se">`"</span><span class="si">$(</span><span class="nv">$certs</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">NotAfter</span><span class="si">)</span><span class="se">`"</span><span class="s2">"</span><span class="w">
</span><span class="nv">$binding</span><span class="o">.</span><span class="nf">AddSslCertificate</span><span class="p">(</span><span class="nv">$newCert</span><span class="o">.</span><span class="nf">Thumbprint</span><span class="p">,</span><span class="w"> </span><span class="s2">"my"</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">})</span><span class="w">
</span></code></pre></div></div>
<p>With a bit of luck, you should see something like this:</p>
<p><img src="/images/cert-output.png" alt="output from script" class="img-responsive" /></p>Lachlan BarclayLet's say you needed to update your SSL certificates on IIS, across a whole lot of servers. Do you want to do it all manually? Heck no. Let's hack something together in powershell to make it work. The method binding.AddSslCertificate() is the one you're looking for!Talking To Doom In 1993 From Discord In 20212021-08-17T00:00:00+00:002021-08-17T00:00:00+00:00https://lachlanbarclay.net/2021/08/Talking-To-Doom-In-1993-From-Discord-In-2021<p>Did you know that multiplayer doom is still alive and kicking? Nope, not Doom Eternal. Not 2016 Doom.. but 1993 doom! Thanks to the legends who work on <a href="https://zandronum.com">Zandronum</a>, you can play deathmatch or co-operative play over the internet using the original doom engine. Except the engine has been improved for the modern age - 3D acceleration, higher resolutions, transparency, better lighting effects… it’s still the original Doom, just <em>more awesomer</em>:</p>
<p><img src="/images/zandronum-aa.png" alt="image-title-here" class="img-responsive" /></p>
<p>A few friends and I started playing Doom online, and we quickly discovered it’s a lot more fun with real-time voice chat. So we added a Discord Server, and we chat over that while we’re playing. It’s great fun! Except… being a nerd… I want <strong>more</strong>.</p>
<p>The problem that I was having was administering the doom server. Simple things like changing maps, changing the difficulty and seeing which players have died, is difficult. It’s hard for anyone else to do, and it’s hard to see what’s going on. What I really wanted was a way of typing commands into Discord and controlling Zandronum. Plus, it would be really nice if we could receive messages from Zandronum, telling us what’s going on.</p>
<h3 id="writing-the-discord-bot">Writing the Discord Bot</h3>
<p>So to get started, I needed to write a c# discord bot. I found a great library named <a href="https://github.com/discord-net/Discord.Net" target="_blank">discord.net</a> that I could use to connect to a discord server and send a message:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">discordClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DiscordSocketClient</span><span class="p">();</span>
<span class="n">discordClient</span><span class="p">.</span><span class="n">MessageReceived</span> <span class="p">+=</span> <span class="p">(</span><span class="n">socket</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">Content</span> <span class="p">==</span> <span class="s">"Hi testbot"</span><span class="p">)</span>
<span class="n">socket</span><span class="p">.</span><span class="n">Channel</span><span class="p">.</span><span class="nf">SendMessageAsync</span><span class="p">(</span><span class="s">"Hi, "</span> <span class="p">+</span> <span class="n">socket</span><span class="p">.</span><span class="n">Author</span><span class="p">.</span><span class="nf">ToString</span><span class="p">());</span>
<span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">await</span> <span class="n">discordClient</span><span class="p">.</span><span class="nf">LoginAsync</span><span class="p">(</span><span class="n">TokenType</span><span class="p">.</span><span class="n">Bot</span><span class="p">,</span> <span class="s">"MyBotTokenCredentials"</span><span class="p">);</span>
</code></pre></div></div>
<p>This little block of code connects to Discord using the <code class="language-plaintext highlighter-rouge">MyBotTokenCredentials</code>, and then if someone types <code class="language-plaintext highlighter-rouge">Hi testbot</code> into the chat it will reply with <code class="language-plaintext highlighter-rouge">Hi -username-</code>. Pretty cool! Of course the first order of the day was to make it reply with the classic Dad joke:</p>
<p><img src="/images/doombot-dadjoke.png" alt="image-title-here" class="img-responsive" /></p>
<p>On reflection this was as good as it was going to get so I probably should have just stopped here. But anyway, there’s a good guide on <a href="https://docs.stillu.cc/guides/getting_started/first-bot.html">setting up a discord.net bot here</a> that I followed if you want to have a crack at it yourself.</p>
<p>So once this was working I needed it to run in Azure, as part of an always-running asp.net application. So I threw it into a <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services">hosted service</a>, which basically looked like this:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="n">DiscordSocketClient</span> <span class="n">_discordClient</span><span class="p">;</span>
<span class="k">protected</span> <span class="k">override</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">ExecuteAsync</span><span class="p">(</span><span class="n">CancellationToken</span> <span class="n">stoppingToken</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// initialise the _discordClient here, then connect:</span>
<span class="p">...</span>
<span class="n">_discordClient</span><span class="p">.</span><span class="n">MessageReceived</span> <span class="p">+=</span> <span class="n">_client_MessageReceived</span><span class="p">;</span>
<span class="k">await</span> <span class="n">_discordClient</span><span class="p">.</span><span class="nf">StartAsync</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">_client_MessageReceived</span><span class="p">(</span><span class="n">SocketMessage</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"!doombot"</span><span class="p">))</span>
<span class="p">{</span>
<span class="p">...</span> <span class="k">do</span> <span class="n">something</span> <span class="n">clever</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Freaking sweet. Oh by the way, one little gotcha is that if you want to send stuff to a channel at bot startup, you have to wait until your bot is online:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">discordClient</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DiscordSocketClient</span><span class="p">();</span>
<span class="kt">bool</span> <span class="n">isReady</span> <span class="p">=</span> <span class="k">false</span><span class="p">;</span>
<span class="n">discordClient</span><span class="p">.</span><span class="n">Ready</span> <span class="p">+=</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">isReady</span> <span class="p">=</span> <span class="k">true</span><span class="p">;</span>
<span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">await</span> <span class="n">discordClient</span><span class="p">.</span><span class="nf">LoginAsync</span><span class="p">(</span><span class="n">TokenType</span><span class="p">.</span><span class="n">Bot</span><span class="p">,</span> <span class="s">"BotToken"</span><span class="p">);</span>
<span class="k">while</span> <span class="p">(!</span><span class="n">_isReady</span><span class="p">)</span>
<span class="n">System</span><span class="p">.</span><span class="n">Threading</span><span class="p">.</span><span class="n">Thread</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="m">1000</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">botTestingChannel</span> <span class="p">=</span> <span class="n">_discordClient</span>
<span class="p">.</span><span class="n">Guilds</span>
<span class="p">.</span><span class="nf">First</span><span class="p">()</span>
<span class="p">.</span><span class="n">Channels</span>
<span class="p">.</span><span class="nf">First</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">"bot-testing"</span><span class="p">)</span> <span class="k">as</span> <span class="n">IMessageChannel</span><span class="p">;</span>
<span class="k">await</span> <span class="n">botTestingChannel</span><span class="p">.</span><span class="nf">SendMessageAsync</span><span class="p">(</span><span class="s">"Hello world!"</span><span class="p">);</</span><span class="n">pre</span><span class="p">></span>
</code></pre></div></div>
<p>That one took me a while to figure out!</p>
<p>So that’s the Discord bot side of things worked out. Now we need to get it to talk to our Doom Server, which is named Zandronum.</p>
<h3 id="talking-to-zandronum-over-udp">Talking to Zandronum over UDP</h3>
<p>Zandronum has two different protocols you can use to talk to it. One is named the <a href="https://wiki.zandronum.com/Launcher_protocol">Launcher Protocol</a> that operates over UDP. It deals with unauthenticated requests, so you can query a public doom server and ask some basic questions like ‘how many players are online’ and ‘what is the current map’. Behind the scenes it’s what tools like <a href="https://doomseeker.drdteam.org/">DoomSeeker</a> use.</p>
<p>I say simple, but of course the devil is in the details. The most complicated part is that every message is compressed using <a href="https://en.wikipedia.org/wiki/Huffman_coding">Huffman coding</a>… but for some reason with a hard-coded customised unique tree. There is some example code provided in java and python…. but nothing dotnet!</p>
<p>Fortunately, <a href="https://twitter.com/bananaboysam">Sam Izzo</a> is a games developer and a very smart guy, and he was able to get the basics of the UDP Launcher Protocol working. We could send messages to the server! Here’s the console app dumping out some basic info:</p>
<p><img src="/images/zandronum-query.png" alt="image-title-here" class="img-responsive" /></p>
<p>The next step was to try to make it do something more interesting - we needed to be able to authenticate and send through some commands. For that, we need to use the <a href="https://wiki.zandronum.com/RCon_protocol">RCon (Remote Console) Protocol</a>. The protocol is very similar, but we have to do a handshake first, and then we can start streaming messages from the doom console, and sending it commands. In this convoluted screenshot, you can see the Zandronum server running, and you can see the console application in the background dumping out what’s going on:</p>
<p><img src="/images/zandronum-zanstat.png" alt="image-title-here" class="img-responsive" /></p>
<h3 id="zandronum-dotnet-nuget-package">Zandronum dotnet NuGet Package</h3>
<p>Once all of this code was up and running, I bundled it into a <a href="https://github.com/rocklan/zanstat">nuget package</a> so that nobody else has to go through the same pain again! It seems to be reasonably stable but I’m sure there’s always bugs.</p>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>Now it’s a matter of gluing the two together. We needed to be able to read and write console data from the zandronum server and stream it back to Discord. So let’s do that:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">zandronum</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Zandronum</span><span class="p">(</span><span class="n">_zandronumServer</span><span class="p">,</span> <span class="n">_zandronumPort</span><span class="p">);</span>
<span class="n">zandronum</span><span class="p">.</span><span class="n">Rcon</span><span class="p">.</span><span class="n">ServerMessage</span> <span class="p">+=</span> <span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">eventArgs</span><span class="p">)</span> <span class="p">=></span>
<span class="p">{</span>
<span class="n">ZandronumMessageEventArgs</span> <span class="n">ea</span> <span class="p">=</span> <span class="n">e</span> <span class="k">as</span> <span class="n">ZandronumMessageEventArgs</span><span class="p">;</span>
<span class="n">botTestingChannel</span><span class="p">.</span><span class="nf">SendMessageAsync</span><span class="p">(</span><span class="n">ea</span><span class="p">.</span><span class="n">Message</span><span class="p">);</span>
<span class="p">};</span>
<span class="n">zandronum</span><span class="p">.</span><span class="n">Rcon</span><span class="p">.</span><span class="nf">ConnectToRcon</span><span class="p">(</span><span class="n">_zandronumRConPassword</span><span class="p">);</span>
</code></pre></div></div>
<p>Now let’s add some bots to a server (in this case, named Chubbs, Crash and Gamma) and see what happens:</p>
<p><img src="/images/doombot-output.png" alt="output of doombot" /></p>
<p>Cool! Now let’s hook up the bot to be able to pass commands back to the server:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">discordClient</span><span class="p">.</span><span class="n">MessageReceived</span> <span class="p">+=</span> <span class="p">(</span><span class="n">socket</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"!doom "</span><span class="p">))</span> <span class="p">{</span>
<span class="kt">string</span> <span class="n">rconCommand</span> <span class="p">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">Substring</span><span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="n">arg</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">6</span><span class="p">);</span>
<span class="n">_zandronum</span><span class="p">.</span><span class="n">Rcon</span><span class="p">.</span><span class="nf">SendCommand</span><span class="p">(</span><span class="n">rconCommand</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Task</span><span class="p">.</span><span class="n">CompletedTask</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And let’s give it a shot:</p>
<p><img src="/images/doombot-commands.png" alt="doombot commands" /></p>
<p>So far it’s been running ok and it’s pretty stable. The discord.net library is very nice in that it handles disconnections and re-connects!</p>
<p>The next step would be to implement <a href="https://blog.discord.com/slash-commands-are-here-8db0a385d9e6">discord’s slash commands</a>… but maybe that’s for version 2! :)</p>Lachlan BarclayDid you know that multiplayer doom is still alive and kicking? Nope, not Doom Eternal. Not 2016 Doom.. but 1993 doom! Thanks to the legends who work on Zandronum, you can play deathmatch or co-operative play over the internet using the original doom engine. Except the engine has been improved for the modern age - 3D acceleration, higher resolutions, transparency, better lighting effects… it’s still the original Doom, just more awesomer:Recommended Multiplayer Doom WADS2021-08-17T00:00:00+00:002021-08-17T00:00:00+00:00https://lachlanbarclay.net/2021/08/Recommended-Multiplayer-Doom-WADS%20copy<p>For a while now a bunch of friends and I have been playing co-operative Doom using custom WADS. These WADS are utterly incredible, vastly improving the original doom with mind-blowing map design, new monsters, new weapons, new sounds & new puzzles. The amount of things that can be done with the Doom engine is amazing, and people are constantly pushing the limits.</p>
<p>So why co-op? We started off playing deathmatch but found that playing co-op was more fun. Working together in Survival Mode to beat a map forces a lot of cooperation and planning, and there’s a lot more at stake when you’re on your last life!</p>
<p>Now finding good co-op maps is difficult. There’s thousands to choose from and you don’t want to waste your time on the boring ones. Some of them aren’t designed for cooperative play. A lot of the time I would go through the <a href="https://www.doomworld.com/cacowards/">Cacowards</a>, pick out a few and try them out, but even then it’s a bit of a guess into what is good. Some of them are just too hard, some are too easy… it’s a difficult balancing act.</p>
<p>The following is a list of WADS that we have played so that we can keep track of what we’ve done, and what we’d like to play next.</p>
<h4 id="awesome-co-op--survival-wads-that-are-strongly-recommended">Awesome co-op / survival wads that are strongly recommended</h4>
<ul>
<li><a href="https://www.doomworld.com/forum/topic/103425-final-release-eviternity/">Eviternity</a> - massive, incredible Mega WAD. Freaking awesome.</li>
<li><a href="https://doomwiki.org/wiki/Valiant">Valiant</a> - Skillsaw is a genius</li>
<li><a href="https://doomwiki.org/wiki/Sunlust">Sunlust</a> - Fun, amazing, very ‘dark and moody’ though.</li>
<li><a href="http://www.doom2.net/toke/">Toke’s Coop Build</a> - Old school WAD with some great ideas. Super creative.</li>
<li><a href="https://www.doomworld.com/forum/topic/105215-lost-civilization-22-map-fun-and-nonlinear-megawad-now-with-download-link/">Lost Civilisation</a> - Utterly fantastic WAD</li>
<li><a href="https://doomwiki.org/wiki/Ancient_Aliens">Ancient Aliens</a> - Amazing WAD. Awesome new monsters, storyline and quirky. So much fun.</li>
<li>Resurge</li>
</ul>
<h4 id="other-wads-that-were-good-fun">Other WADS that were good fun</h4>
<ul>
<li><a href="https://www.doomworld.com/idgames/deathmatch/skulltag/du_se">Destination Unknown</a></li>
<li><a href="https://zandronum.com/forum/viewtopic.php?t=7919">DoomWare</a> - This is one CRAZY WAD. Hilarious, but gets boring pretty quickly!</li>
<li><a href="https://doomwiki.org/wiki/Antaresian_Reliquary">Antaresian Reliquary</a> - Really good, if a bit unbalanced… MAP04 will absolutely kick your butt. We spent 3 weekends on it!!</li>
</ul>Lachlan BarclayFor a while now a bunch of friends and I have been playing co-operative Doom using custom WADS. These WADS are utterly incredible, vastly improving the original doom with mind-blowing map design, new monsters, new weapons, new sounds & new puzzles. The amount of things that can be done with the Doom engine is amazing, and people are constantly pushing the limits.Moving Dotnet App To Linux And Docker2021-06-02T00:00:00+00:002021-06-02T00:00:00+00:00https://lachlanbarclay.net/2021/06/Moving-Dotnet-App-To-Linux-And-Docker<p>A few months back, I decided to convert an existing .NET core application to run on Linux. As part of this work, I decided to run it inside a Docker container, which meant I could have my application running on Linux without worrying about setting up all of the dependencies on the server like .NET runtime, Apache, etc.</p>
<p>Now I fully realise that Windows containers exist and that I could have tried to get them to work. But the aim for me wasn’t to run on Docker, it was to run on Linux. Docker just came in handy because it meant I didn’t need to install all dependencies onto my Linux server – I could just fire up Docker. </p>
<p>Now, much like communism, Docker is pretty great--in theory. Once the process begins, however, you quickly realise just how many worms are inside the can you’ve just opened. </p>
<p>But unlike communism, in the end, this worked! Here’s the output from my web application showing some server details. Notice any differences?</p>
<p><img data-src="/pics/moving-to-linux-0.png" alt="moving .NET application to linux 0" class="img-responsive lazyload" /></p>
<p>So with that in mind, here's a list of issues that you should probably know about.</p>
<h3>Table Of Contents</h3>
<ul>
<li><a href="#building-the-docker-image">Building the docker image</a>
<ul>
<li><a href="#image-layers-and-caching">Image Layers and caching</a></li>
</ul>
</li>
<li><a href="#startup">Getting the app to start up</a>
<ul><li><a href="#localhost">Localhost</a> </li>
<li><a href="#third-party-libs">Support for third party libraries</a></li>
<li><a href="#windows-auth">Windows Authentication</a></li>
</ul>
</li>
<li><a href="#runtime">Runtime Errors</a>
<ul><li>
<a href="#system-fonts">System fonts</a> </li><li>
<a href="#unspecified-culture">Unspecified Culture</a> </li><li>
<a href="#https-and-certs">HTTPS and Certificates</a> </li><li>
<a href="#timezones">Timezones</a></li><li>
<a href="#network-share-paths">Network Share Paths</a> </li><li>
<a href="#case-sensitivity">Case sensitive URLs</a> </li>
</ul>
</li>
<li><a href="#misc">Other Things </a>
<ul><li>
<a href="#disk-space">Disk Space</a> </li><li>
<a href="#automated-builds-and-deploys">Automated Builds and Deploys</a> </li><li>
<a href="#security">Security</a> </li>
</ul>
</li>
<li><a href="#the-aftermath">The Aftermath</a> </li>
</ul>
<h2 id="building-the-docker-image">Building the Docker image</h2>
<h3 id="image-layers-and-caching">Image Layers and Caching</h3>
<p>The first step was to package my application up inside a Docker image. Microsoft has some great articles on how to do this, but they are the usual only-works-for-a-simple-demo examples. My application is a bit larger and has multiple project files--hardly novel and hardly new. Fortunately, a programmer named Tometchy has a great blog post on <a href="https://www.softwaredeveloper.blog/multi-project-dotnet-core-solution-in-docker-image">how to set this up</a>. After following his advice, I created my new Dockerfile and ran:</p>
<pre><code class="hljs">docker build src/myproject/Dockerfile</code></pre>
<p>... and it built! However... it was taking forever!</p>
<p>Unless you structure your Dockerfile in a very particular way, your NuGet package restores will always take forever to restore. Instead of using a local package cache, .NET will do a complete NuGet restore for every build. This means builds will take minutes instead of the usual seconds. So instead of doing this:</p>
<pre><code class="hljs">COPY . ./
Dotnet build
</code></pre>
<p>
You can take advantage of Docker’s layer caching like so:
</p>
<pre><code class="hljs">COPY src/myproject/myproject.csproj
COPY nuget.config ./nuget.config
RUN dotnet restore src/myproject/myproject.csproj \
--configfile nuget.config \
--packages packages
COPY . ./
RUN dotnet build
</code></pre>
<p>
This means that every new Docker build won’t do a full NuGet restore—that will only happen when you change your project file. This meant I could build my images in a few seconds. Now that it was working, it was time to run my application. </p>
<h3 id="startup">
Getting the app to start up</h3>
<h4 id="localhost">Localhost</h4>
<p>When running an app inside Docker, <code>localhost</code> suddenly isn't <code>localhost</code>. The network has been virtualised! <code>localhost</code> doesn't mean what you think it means, it’s now referring to the Docker container itself, not the host. There is a simple fix for this - use <code>host.docker.internal</code> instead of <code>localhost</code>. This will work when developing locally, but when running on a server you’re probably better off specifying the full hostname and hope that DNS is set up correctly. Once I changed this, at least my application could connect to my Seq instance to start logging startup errors.</p>
<h4 id="third-party-libs">Support for third party libraries</h4>
<p>Once I fixed my references to <code>localhost</code>, the next problem that I had was not all of my DLL’s were loading properly. At startup I would get this error:</p>
<p><img data-src="/pics/moving-to-linux-2.png" alt="moving dotnet application to linux 2" class="img-responsive lazyload" /></p>
<p>For PDF management I’m using a library named <a href="https://www.websupergoo.com/abcpdf-1.aspx">AbcPdf</a>. It’s a great library with heaps of functionality that I’ve been using for years. There’s just one problem. AbcPdf isn't supported on Linux. There are just no Linux binaries, full stop. Sure, this was hidden inside their documentation somewhere, but the only way to find this out was to actually launch my application and see if it crashed at startup.</p>
<p>
This was pretty confusing, because the code worked great on Windows. (I particularly like the stack trace with unreadable characters). But after a bit of googling on their website, I found <a href="https://www.websupergoo.com/helppdfnet/source/3-concepts/6-coreandstandard.htm">this gem</a>:</p>
<blockquote>Windows only – not Linux or Xamarin</blockquote>
<p>This was a problem. My only options were:</p>
<ol>
<li>Investigate other PDF libraries that have Linux support</li>
<li> Refactor out all of the AbcPdf code and run it on Windows</li>
</ol>
<p><br /></p>
<p>I chose Option 2. I refactored out all of the code to a separate application that I would run on Windows and slapped an API wrapper around it. This was a bit of work, but there wasn’t that much code there, and what was there could be pulled out pretty easily. After a day, I ended up with a nice API for creating and merging PDFs:</p>
<p><img data-src="/pics/moving-to-linux-3.png" alt="moving dotnet application to linux 3" class="img-responsive lazyload" /></p>
<p>This was also quite nice as I knew there was another application that needed some PDF functionality, and now I had a nice and reusable API to call.</p>
<p>The next thing that I needed to do was to check that all of my other dependencies had Linux support. So here’s the full list of libraries that I’m using:</p>
<ol>
<li> Hangfire - long running tasks </li>
<li> Serilog + Seq - logging </li>
<li> Swashbuckle - swagger API </li>
<li> AWS SDK - talking to S3 </li>
<li> EPPlus - excel file manipulation </li>
<li> MongoDB </li>
</ol>
<p>Fortunately, all of them have fantastic Linux support. Except for the one that didn’t.</p>
<h3 id="windows-auth">Windows Authentication</h3>
<p>The next problem was happening at startup:</p>
<pre><code class="hljs">System.InvalidOperationException: No authenticationScheme was specified,
and there was no DefaultChallengeScheme found.
The default schemes can be set using either AddAuthentication(string defaultScheme) or
AddAuthentication(Action<AuthenticationOptions> configureOptions).
</code></pre>
<p>This didn’t make any sense. It’s the same code from Windows… but the more I looked at it, I realised that this was probably the culprit:</p>
<p><img data-src="/pics/moving-to-linux-4.png" alt="moving dotnet application to linux 4" class="img-responsive lazyload" /></p>
<p>First off, the app isn’t using IIS, so the chances are, IIS authentication probably isn’t going to work. So after much googling, I found that while it is possible to get windows authentication working on Linux, to set it up you need to <a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth">have access to a domain controller</a> – something that I expect no developer would have--or should have! This is clearly not a practical solution. My solution was to move Windows auth out of the Linux server and move it to a different application (a topic for another post). But, to get you started, I found out I could have the application use IIS auth for when running under Windows, and a different auth for when running under Linux:</p>
<p><img data-src="/pics/moving-to-linux-5.png" alt="moving dotnet application to linux 5" class="img-responsive lazyload" /></p>
<p>This is kind of cool, but really it probably should be a config that is set at startup, not determined by the code at runtime depending on the OS that it’s running on.</p>
<h2 id="runtime">Runtime Errors</h2>
<p>My application was now loading! I could click around and it was responding. It barely worked, but hey, it was loading. Now to start fixing all of the runtime errors.</p>
<h3 id="system-fonts">System fonts</h3>
<p>Inside my application I had a little bit of code that created a list of all of the system fonts installed on the server:</p>
<p><img data-src="/pics/moving-to-linux-6.png" alt="moving dotnet application to linux 6" class="img-responsive lazyload" /></p>
<p>When this code ran, I got this error:</p>
<p><img data-src="/pics/moving-to-linux-7.png" alt="moving dotnet application to linux 7" class="img-responsive lazyload" /></p>
<p>Not the most helpful error message I've ever seen, but needless to say, managing font files just doesn't work. I managed to find this <a href="https://github.com/dotnet/runtime/issues/27200">github issue</a> that had a few workarounds, but I never got around to trying them. I ended up moving the same code to the Pdf Api that I mentioned earlier. </p>
<p>This is the kind of thing that ended up being quite common when testing my application on Linux. Something causes the app to crash that normally works on Windows, you google it to find a couple of possible workarounds and try out a few. </p>
<h3 id="unspecified-culture">Unspecified Culture</h3>
<p>Notice anything weird going on here?</p>
<p><img data-src="/pics/moving-to-linux-8.png" alt="moving dotnet application to linux 8" class="img-responsive lazyload" /></p>
<p>Ever seen the ¤ character before? Neither had I, but apparently it's the currency sign used to denote an <a href="https://en.wikipedia.org/wiki/Currency_sign_(typography)">unspecified currency</a>. This means you need to set the culture inside your code instead of relying on the server’s culture:</p>
<p><img data-src="/pics/moving-to-linux-9.png" alt="moving dotnet application to linux 9" class="img-responsive lazyload" /></p>
<p>Problem solved. That was a good one!</p>
<h3 id="https-and-certs">HTTPS and Certificates</h3>
<p>Linux and Windows handle certificate trusting differently. One of the API’s that I’m calling had a certificate that used older ciphers. Apparently, in windows this was fine, but inside Linux, this was not fine and the SSL connection was rejected. I ended up having to modify the runtime openssl configuration inside the Docker container:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RUN sed 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/' \
/etc/ssl/openssl.cnf > /etc/ssl/openssl.cnf.changed && \
sed 's/TLSv1.2/TLSv1.1/' /etc/ssl/openssl.cnf.changed \
/etc/ssl/openssl.cnf.changed2 && \
mv /etc/ssl/openssl.cnf.changed2 /etc/ssl/openssl.cnf
</code></pre></div></div>
<p>Yikes. Isn't it great how docker simplifies things?</p>
<h3 id="timezones">Timezones</h3>
<p>When you make a call to fetch a list of Timezones, the list that is returned is completely different when running on Linux compared to Windows! Apparently this is because the list of time zones is returned by the OS... and of course the two do it completely differently. There is a library to <a href="https://devblogs.microsoft.com/dotnet/cross-platform-time-zones-with-net-core/">work around this</a>, but boy is it a doozy to catch during runtime.</p>
<h3 id="network-share-paths">Network Share Paths</h3>
<p>Writing to a Windows network share is suddenly much more difficult. My solution was to mount the network share on the host using samba (via smbclient), and then mapping this mounted share to a Docker volume, and then reading from that. It then finally works, after only adding two more levels of abstraction! But… can you change the permissions on the newly created file? Let’s try it:</p>
<p><img data-src="/pics/moving-to-linux-10.png" alt="moving dotnet application to linux 10" class="img-responsive lazyload" /></p>
<p>Oh well, I guess not. This was annoying as it meant I couldn't change basic file permissions like making it readonly, or do something more advanced like change the owner of the file. </p>
<p>After a while you kind of get used to seeing messages that effectively say "this stuff works on Windows but not on Linux".</p>
<h3 id="case-sensitivity">Case sensitive URLs</h3>
<p>My URLs needed to be the correct case! Previously a url like <code>/content/MyFile.js</code> would load, even if the actual file on disk was stored as <code>/content/myfile.js</code>. However now that my app is running on Linux, case sensitivity is an issue. This wasn’t too much of a problem, but it caught me out a few times.</p>
<h2 id="misc">Other Things</h2>
<p>There were a few other things that I needed to keep in mind.</p>
<h3 id="disk-space">Disk Space</h3>
<p>When building Docker images, I would constantly run out of disk space. It’s really annoying and it's quite difficult to troubleshoot andwork out where all of the space is going and how to clean it up. Yes, you have the Docker prune command, but that wasn’t really working. I ended up having to impose some pretty strict “only use this much space” policies to my hyper-v image, which were really difficult to get right. </p>
<p>If you're not careful, the Docker images can become huge. One of the third-party libraries that I was using was putting 100 MBs of stuff in my application's bin directory. This meant every -Docker image push+pull would be using a lot more bandwidth than I expected. Easily done and difficult to spot. </p>
<h3 id="automated-builds-and-deploys">Automated Builds and Deploys</h3>
<p>My Automated build was suddenly very different. Instead of using the normal templated solution that I used for all of my other applications I suddenly need to have a custom build for Docker. The deploys are also completely different - better, MUCH better, but oh so different. Instead of pushing the package to the web server from your CI server, you pull it from the image hub directly from the web server. A very different way of thinking about it. Not something that I really thought through, and something that will need a bit of effort put in.</p>
<h3 id="security">Security</h3>
<p>You need to keep passwords and all other secrets out of your image. This means passing through all secrets as environment variables and making sure your application is loading them in at runtime. Nothing extraordinary here, but some more work that needed to be done to make sure it was working, and setting up an env.list file. </p>
<h2 id="the-aftermath">The Aftermath</h2>
<p>So after all of those woes, I managed to get my application fully up and running:</p>
<p><img data-src="/pics/moving-to-linux-13.png" alt="moving dotnet application to linux 13" class="img-responsive lazyload" /></p>
<p>I added a new page to dump out some runtime information, and it now displays the following: </p>
<p><img data-src="/pics/moving-to-linux-12.png" alt="moving dotnet application to linux 12" class="img-responsive lazyload" /></p>
<p>This is pretty cool! This opens up a world of possibilities:</p>
<ul>
<li> My application runs faster. </li>
<li> .NET on Linux performs better than on Windows. </li>
<li> It uses less memory and is able to handle more traffic</li>
<li> Deploys are vastly improved:
<ul>
<li>
They are much simpler - just pull down the Docker image and run it</li>
<li> A lot faster - practically zero downtime as it only takes a second to startup</li>
<li> Instant roll-back – running a previous version only takes a second</li>
</ul></li>
<li> Scaling out my application is much easier – just run the Docker image on another server </li>
<li> My app can run easily on Azure or AWS</li>
<li> My Docker image can be scanned for out-of-date dependencies or security problems</li>
<li> Integration tests are now much easier to run.I can create a Docker compose file that sets up the entire environment (database, API’s, etc.) and runs my tests</li>
<li> I can upgrade to a newer version of the Dotnet runtime without having to worry about what’s installed on the server</li>
<li> I can run my app on a Kubernetes cluster</li>
</ul>
<p>
While moving to Docker wasn’t exactly a walk in the park, now that I have that knowledge moving my other applications won’t be nearly as difficult.
</p>
<h2>Workshops!</h2>
<p>Great job on reading this far! If you're interested in this kind of stuff I'm currently running two different workshops on similar topics:
</p>
<ol>
<li><a href="https://dotnetworkshops.com/workshops/optimise-your-code-with-visual-studios-profiler">Optimising your code with Visual Studio's Profiler</a></li>
<li><a href="https://dotnetworkshops.com/workshops/porting-your-aspnet-app-from-framework-to-core">Porting your asp dotnet application from framework to core</a></li>
</ol>
<p>Thanks for reading and remember... <b>you don't need permission to be awesome</b>.</p>Lachlan BarclayA few months back, I decided to convert an existing .NET core application to run on Linux. As part of this work, I decided to run it inside a Docker container, which meant I could have my application running on Linux without worrying about setting up all of the dependencies on the server like .NET runtime, Apache, etc.