<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Dean Hume's Blog]]></title><description><![CDATA[I am an author, blogger and I am passionate about making games. Welcome to my website.]]></description><link>https://deanhume.com/</link><image><url>https://deanhume.com/favicon.png</url><title>Dean Hume&apos;s Blog</title><link>https://deanhume.com/</link></image><generator>Ghost 5.38</generator><lastBuildDate>Sun, 05 Apr 2026 17:11:43 GMT</lastBuildDate><atom:link href="https://deanhume.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[How I Use PowerToys Workspaces to Switch Contexts in Two Clicks 🫰]]></title><description><![CDATA[Tired of arranging windows every morning? PowerToys Workspaces launches your ideal Windows desktop management setup with two clicks 🫰.]]></description><link>https://deanhume.com/how-i-use-powertoys-workspaces-to-switch-contexts-in-two-clicks/</link><guid isPermaLink="false">69a9dfa2cad3b005dffc0bfe</guid><category><![CDATA[Technical Program Manager]]></category><category><![CDATA[Writing]]></category><category><![CDATA[Powertoys]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Thu, 12 Mar 2026 14:32:13 GMT</pubDate><media:content url="https://deanhume.com/content/images/2026/03/powertoys-workspaces.png" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2026/03/powertoys-workspaces.png" alt="How I Use PowerToys Workspaces to Switch Contexts in Two Clicks &#x1FAF0;"><p>Have you ever sat down at your computer, ready to get into a flow state, and then spent the first ten minutes just <em>arranging windows</em>? Dragging your editor here, your browser there, nudging Slack into a corner - only to close it all two hours later, and start from scratch the next day? Yeah. Me too.</p><p>I&apos;ve been a big fan of <a href="https://learn.microsoft.com/en-us/windows/powertoys/">Microsoft PowerToys</a> for a while now. It&apos;s one of those tools that quietly makes your Windows experience feel a lot more intentional. There are a bunch of great utilities in there - but recently I&apos;ve been leaning heavily on one in particular: <strong>PowerToys Workspaces</strong>.</p><h2 id="what-is-powertoys-workspaces">What is PowerToys Workspaces?</h2><p>Workspaces is a Windows desktop management utility that lets you capture your ideal window layout, which apps are open, where they&apos;re positioned, what size they are, and then launch that entire setup with a single click.</p><p>Think of it like a saved game state, but for your desktop.</p><p>You could have a &quot;Coding&quot; workspace that opens VS Code to a specific project, a browser on the side, and Terminal ready to go. Or an &quot;Email &amp; Admin&quot; workspace that brings up Outlook, your calendar, and a notes app. Whatever your workflow looks like - you set it up once, and Workspaces handles the rest every time.</p><h2 id="setting-up-your-first-workspace">Setting Up Your First Workspace</h2><p>If you haven&apos;t already got <a href="https://learn.microsoft.com/en-us/windows/powertoys/">PowerToys </a>installed, you can grab it from the <a href="https://aka.ms/getPowertoys">Microsoft Store</a> or via <a href="https://winget.run/pkg/Microsoft/PowerToys">WinGet</a>. Once it&apos;s running, head into the PowerToys Settings and enable <strong>Workspaces</strong>.</p><p>To create your first workspace:</p><ol><li>Hit <code>Win + Ctrl + &apos; </code> to open the Workspaces editor (or go to Settings and click &quot;Launch editor&quot;).</li><li>Click <strong>&quot;+ Create workspace&quot;.</strong></li><li>You&apos;ll enter Capture mode - arrange your apps exactly how you want them.</li><li>When you&apos;re happy hit <strong>&quot;Capture&quot;.</strong></li><li>Give it a name, tweak anything you need to, and save it.</li></ol><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2026/03/image.png" class="kg-image" alt="How I Use PowerToys Workspaces to Switch Contexts in Two Clicks &#x1FAF0;" loading="lazy" width="1417" height="567" srcset="https://deanhume.com/content/images/size/w600/2026/03/image.png 600w, https://deanhume.com/content/images/size/w1000/2026/03/image.png 1000w, https://deanhume.com/content/images/2026/03/image.png 1417w" sizes="(min-width: 720px) 720px"></figure><p>That&apos;s it. When it you launch it, it will open those apps exactly as you&apos;d like them. You can create as many different workspaces as you&apos;d like depending on your needs.</p><h2 id="the-part-that-surprised-me">The Part That Surprised Me</h2><p>What I didn&apos;t expect was how useful the <strong>CLI arguments</strong> feature would be. When you&apos;re in the editor, each app has a little dropdown where you can pass command line arguments. So for VS Code, I can point it straight to a specific project folder on launch. For Edge, I can give it a comma-separated list of URLs and it&apos;ll open all those tabs automatically.</p><p>It sounds like a small thing - but removing those few extra clicks every time you start a session adds up surprisingly quickly.</p><h2 id="desktop-shortcut">Desktop Shortcut</h2><p>Within the workspace editor, you can select the option to <strong>Create a desktop shortcut</strong> which creates a handy shortcut on your desktop.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://deanhume.com/content/images/2026/03/image-1.png" class="kg-image" alt="How I Use PowerToys Workspaces to Switch Contexts in Two Clicks &#x1FAF0;" loading="lazy" width="1662" height="1234" srcset="https://deanhume.com/content/images/size/w600/2026/03/image-1.png 600w, https://deanhume.com/content/images/size/w1000/2026/03/image-1.png 1000w, https://deanhume.com/content/images/size/w1600/2026/03/image-1.png 1600w, https://deanhume.com/content/images/2026/03/image-1.png 1662w" sizes="(min-width: 720px) 720px"><figcaption>A shortcut on my desktop made by PowerToys Workspaces</figcaption></figure><p>I find this super handy for when I start my PC first thing in the morning. Just a double click and your workspace is loaded and ready to go. If you prefer, you could pin it to your taskbar if you really want it front and centre.</p><h2 id="a-few-things-worth-knowing">A Few Things Worth Knowing</h2><p>When a workspace launches, you&apos;ll briefly see windows jumping around the screen as PowerToys repositions them. There&apos;s a status dialog that pops up to show you what&apos;s loading, which helps - but it can look a bit chaotic the first time you see it.</p><p>Also worth noting: if you&apos;ve got apps snapped using Windows&apos; built-in snap feature, Workspaces won&apos;t preserve that snapped state. It uses its own positioning engine under the hood. Not a dealbreaker, but something to be aware of when you&apos;re setting things up.</p><h2 id="how-i-use-it-day-to-day">How I Use It Day to Day</h2><p>If you&apos;ve read my recent post on <a href="https://deanhume.com/staying-technical-as-a-technical-program-manager/">staying technical as a TPM</a>, you&apos;ll know I&apos;m always looking for ways to reduce friction and stay in a coding mindset. Workspaces has become one of those small but surprisingly impactful habits.</p><p>I&apos;ve ended up with two workspaces that cover probably 90% of my week:</p><ul><li><strong>Coding</strong> - VS Code, Terminal, and a browser window with the relevant docs.</li><li><strong>Daily</strong> - Outlook, Teams, Slack and a browser.</li></ul><p>Switching between them takes a double click. It sounds almost too simple - but honestly, that&apos;s the point. The friction of setting up your environment is just gone.</p><hr><p>If you haven&apos;t explored PowerToys yet, it&apos;s one of the best free productivity tools for Windows and the <a href="https://learn.microsoft.com/en-us/windows/powertoys/workspaces">Workspaces docs</a> are a great place to start.</p>]]></content:encoded></item><item><title><![CDATA[Staying Technical as a Technical Program Manager]]></title><description><![CDATA[Learn practical ways Technical Program Managers can stay technical - daily habits, internal engineering practices, and hands-on routines that keep skills sharp while leading programs at scale.]]></description><link>https://deanhume.com/staying-technical-as-a-technical-program-manager/</link><guid isPermaLink="false">6904c536cad3b005dffc09d8</guid><category><![CDATA[Game Development]]></category><category><![CDATA[Leadership]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Wed, 18 Feb 2026 11:39:39 GMT</pubDate><media:content url="https://deanhume.com/content/images/2026/02/coding.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2026/02/coding.jpg" alt="Staying Technical as a Technical Program Manager"><p>In my career, I&apos;ve been many things - a Support Engineer, a Software Engineer and an Engineering Manager. These days, I&apos;ve settled into a Technical Program Manager role (which I am thoroughly enjoying).</p><p>As a Technical Program Manager (or TPM), you&apos;re often expected to wear multiple hats - strategic planner, cross-functional communicator, and technical problem solver. But how do you stay technically sharp while managing programs at scale?</p><p>The role of a TPM is different from a program manager, you <em>are </em>expected to be <strong>technical</strong>. Here are some of the daily (and weekly) practices that help me keep my technical skills sharp.</p><h3 id="writing-the-occasional-code">Writing the occasional code</h3><p>Early in my career, I was a software engineer. These days, my role doesn&#x2019;t require me to write code, but I still have the urge to explore and tinker. I&#x2019;ve built a bunch of small tools and love updating my <a href="https://github.com/deanhume/">GitHub account</a>. I&#x2019;m also big into AI right now (as is everyone &#x1F602;) and have been doing a lot of &#x201C;vibe coding&#x201D; just to learn something new or scratch an itch. Even writing a few lines here and there keeps your brain tuned to real engineering challenges.</p><h3 id="writing-technical-content">Writing Technical Content</h3><p>Blogging has been a powerful tool for learning. Whether it&#x2019;s writing about <a href="https://deanhume.com/azure-hybrid-and-embedded-text-to-speech/">Azure Text-to-Speech</a>, troubleshooting <a href="https://deanhume.com/tag/ghost-tag/">Ghost CMS</a>, or <a href="https://deanhume.com/tag/game-development/">Game Development</a>, the act of explaining technical concepts forces clarity and deepens my understanding. It also helps others, which is always a bonus.</p><h3 id="following-tech-blogs">Following tech blogs</h3><p>One of the most useful habits I&#x2019;ve developed is staying up to date with what&#x2019;s happening across the tech world. Even if it&#x2019;s not tech I use every day, it&#x2019;s important to see how others are solving problems. You never know when a piece of tech or a pattern will end up being useful later.</p><h3 id="insert-your-hobby-here">&lt;Insert your hobby here&gt;</h3><p>Outside of work, I love brewing my own beer. I even have a <a href="https://humebrew.com/">blog</a> dedicated to it! One of the best ways to learn something new is to combine your hobby with tech. Recently, I built a <a href="https://deanhume.com/using-a-raspberry-pi-to-track-the-progress-of-your-homebrew/">fermentation tracker using a Raspberry Pi</a> and temperature sensors. It keeps me close to hardware and scripting, and it&#x2019;s a fun, low-stakes way to apply engineering principles.</p><p>Balancing technical depth with program management isn&#x2019;t about doing everything - it&#x2019;s about staying curious, being useful, and knowing when to dive in. Whether it&#x2019;s through side projects, internal tools, or collaborative problem&#x2011;solving, staying hands&#x2011;on helps me lead with more confidence and empathy. It might all sound obvious, but the real impact has come from deliberately doing these things, day after day, with intention.</p><h2 id="how-to-stay-close-to-your-companys-tech">How to Stay Close to Your Company&apos;s Tech</h2><p>While it&#x2019;s important to understand what&#x2019;s happening across the wider tech landscape, it&#x2019;s just as important (if not more so) to stay close to the technology <em>inside</em> your own organisation. Knowing how your systems work, how your teams build, and where the real challenges live helps you make better decisions and builds trust with engineering.</p><p>Here are some of the things I try to do in my role to stay close to the tech:</p><p><strong>Read Your Company&#x2019;s Engineering Docs</strong><br>Most teams have design docs, architecture overviews, or RFCs floating around. Even skimming them gives you a deeper understanding of how things fit together and why certain decisions were made.</p><p><strong>Sit In on Architecture or Design Reviews</strong><br>You don&#x2019;t necessarily need to weigh in on every detail, but observing the discussions helps you understand trade&#x2011;offs, constraints, and how engineers think about the system.</p><p><strong>Spin Up a Local Dev Environment</strong><br>Even if you&#x2019;re not coding every day, setting up and running the services your teams build helps you understand the tooling, dependencies, and workflows they use.</p><p><strong>Use the Product Like a Real User</strong><br>If your company makes consumer or developer-facing tools, actually use them end-to-end. It sharpens your instincts for what &#x201C;good&#x201D; feels like and what&#x2019;s actually painful.</p><h3 id="why-staying-technical-matters-for-tpms"><strong>Why Staying Technical Matters for TPMs</strong></h3><ul><li><strong>Credibility with Engineering Teams</strong>: Understanding the tech stack helps you ask the right questions and gain trust. This is especially important in a gaming role! If engineers don&apos;t believe you are credible and have the background knowledge, it becomes much harder to lead effectively.</li><li><strong>Better Decision-Making</strong>: Technical fluency helps you assess trade-offs, spot risks, and understand what &#x201C;good&#x201D; looks like.</li><li><strong>Faster Problem Resolution</strong>: When you understand the underlying systems, you can catch bottlenecks or misalignments earlier.</li></ul><h3 id="time-management">Time Management</h3><p>So how do I fit all this learning time in? </p><p>In a busy work environment, it can be tricky to stay current while balancing program responsibilities. I also have a family, and being a present dad is non-negotiable.</p><p>One thing that has helped tremendously is <em>deliberately</em> blocking time for learning or tinkering - without compromising program delivery. For example, I use <a href="https://feedly.com/">Feedly </a>to read all the RSS feeds I subscribe to. First thing in the morning with a cup of coffee, I catch up on what&apos;s happening in our industry.</p><p>At Microsoft, we also have<a href="https://techcommunity.microsoft.com/blog/mvp-blog/microsoft-global-hackathon-2025-mvps-driving-innovation-across-communities/4442513"> company-wide hackathons</a>, and they&#x2019;re a fantastic opportunity to try something new, collaborate with people you&#x2019;ve never worked with, and solve real technical challenges. I try to participate whenever I can.</p><p>Balancing learning and work isn&#x2019;t easy, but scheduling time for both makes it achievable.</p><h3 id="conclusion">Conclusion</h3><p>I hope you&#x2019;ve found this post useful. These are the things that have personally helped me stay technically sharp - they might not all be right for you, but it&#x2019;s a good place to start. For me, it ultimately comes down to staying curious, being deliberate, and consistently carving out time to keep learning.</p><p>If you&apos;re a Technical Program Manager, how do you stay technical? I&#x2019;d love to hear your approach.</p>]]></content:encoded></item><item><title><![CDATA[Backup Buddy: A Simple Tool to Backup Your Website Content]]></title><description><![CDATA[<p>Have you ever wanted to create a backup of your website or blog? Maybe you&apos;re migrating to a new platform, or perhaps you just want a portable archive of your content that you can read offline. I&apos;ve previously <a href="https://deanhume.com/ghost-blog-fixing-no-space-left-on-device-issue">experienced</a> <a href="https://deanhume.com/amazon-aws-ec2-ghost-cms-setup/">issues </a>with this site going down and</p>]]></description><link>https://deanhume.com/backup-buddy-a-simple-tool-to-backup-your-website-content/</link><guid isPermaLink="false">69243079cad3b005dffc0abc</guid><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Mon, 01 Dec 2025 12:33:06 GMT</pubDate><media:content url="https://deanhume.com/content/images/2025/11/logo.png" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2025/11/logo.png" alt="Backup Buddy: A Simple Tool to Backup Your Website Content"><p>Have you ever wanted to create a backup of your website or blog? Maybe you&apos;re migrating to a new platform, or perhaps you just want a portable archive of your content that you can read offline. I&apos;ve previously <a href="https://deanhume.com/ghost-blog-fixing-no-space-left-on-device-issue">experienced</a> <a href="https://deanhume.com/amazon-aws-ec2-ghost-cms-setup/">issues </a>with this site going down and I wanted to make sure that I wasn&apos;t stuck without a backup of all my posts. </p><p>That&apos;s exactly why I built <strong>Backup Buddy</strong> - a command-line tool that turns any website into a collection of clean, readable Markdown files with all the images (and videos) preserved.</p><h2 id="what-does-backup-buddy-do">What Does Backup Buddy Do?</h2><p>Backup Buddy takes a website&apos;s sitemap and automatically downloads every page, converting the HTML into clean Markdown format while saving all the images (and videos) locally. Think of it as creating a time capsule of your website that you can read, search, and store anywhere - no internet required.</p><p>The best part? You end up with content in Markdown format, which means you can:</p><ul><li>Read it in any text editor</li><li>Import it into a static site generator like Hugo or Jekyll</li><li>Search through it with standard text tools</li><li>Version control it with Git</li><li>Share it without worrying about broken links or missing images</li></ul><h2 id="how-to-use-it">How to Use It</h2><p>Using Backup Buddy is straightforward. You just need to know your website&apos;s sitemap URL (usually something like <code>https://yoursite.com/sitemap.xml</code>) and run a single command:</p><pre><code class="language-bash">backup-buddy https://yoursite.com/sitemap.xml
</code></pre><p>That&apos;s it! The tool will:</p><ol><li>Download your sitemap</li><li>Find all the URLs</li><li>Download each page</li><li>Convert the HTML to Markdown</li><li>Save all the images</li><li>Organize everything into neat folders</li></ol><h3 id="getting-started">Getting Started</h3><p>Head over to the Git repo and <a href="https://github.com/deanhume/backup-buddy/releases/tag/v1">download the latest release</a>.</p><h2 id="what-you-get">What You Get</h2><p>After running Backup Buddy, you&apos;ll find an <code>output</code> folder with all your content organized like this:</p><pre><code>output/
&#x251C;&#x2500;&#x2500; 1_my-first-post/
&#x2502;   &#x251C;&#x2500;&#x2500; my-first-post.md
&#x2502;   &#x251C;&#x2500;&#x2500; metadata.txt
&#x2502;   &#x251C;&#x2500;&#x2500; images/
&#x2502;   &#x2502;   &#x251C;&#x2500;&#x2500; header-image.jpg
&#x2502;   &#x2502;   &#x2514;&#x2500;&#x2500; diagram.png
&#x2502;   &#x2514;&#x2500;&#x2500; videos/
&#x2502;       &#x2514;&#x2500;&#x2500; demo.mp4
&#x251C;&#x2500;&#x2500; 2_another-post/
&#x2502;   &#x251C;&#x2500;&#x2500; another-post.md
&#x2502;   &#x251C;&#x2500;&#x2500; metadata.txt
&#x2502;   &#x2514;&#x2500;&#x2500; images/
&#x2502;       &#x2514;&#x2500;&#x2500; photo.jpg
&#x2514;&#x2500;&#x2500; ...
</code></pre><p>Each page gets its own folder containing:</p><ul><li>The Markdown version of your content</li><li>A metadata file with the original URL and backup date</li><li>An images folder with all pictures from that page</li><li>A videos folder with any videos from that page</li></ul><h2 id="why-i-built-this">Why I Built This</h2><p>I&apos;ve been blogging for years, and I&apos;ve migrated platforms more than once. Each time, I worried about losing content, breaking image links, or dealing with complicated export tools. I wanted something simple that would give me a clean backup I could actually use.</p><p>The Markdown format is perfect because it&apos;s:</p><ul><li><strong>Human-readable</strong> - You can open it in any text editor</li><li><strong>Future-proof</strong> - It&apos;s just text, so it&apos;ll work forever</li><li><strong>Portable</strong> - Easy to import into almost any blogging platform or static site generator</li><li><strong>Git-friendly</strong> - You can track changes and collaborate on content</li></ul><h2 id="performance-features">Performance Features</h2><p>One thing I made sure to include was speed. Backup Buddy processes multiple pages at once (10 by default), so even large websites with hundreds of pages get backed up quickly. You&apos;ll see real-time progress as it works:</p><pre><code>[1/150] Processing: https://example.com/first-post
[2/150] Processing: https://example.com/second-post
  &#x2713; Saved to: output/1_first-post
  &#x2713; Saved to: output/2_second-post
[3/150] Processing: https://example.com/third-post
...
</code></pre><h2 id="a-few-things-to-know">A Few Things to Know</h2><p>While Backup Buddy handles most websites well, there are a few limitations:</p><ul><li>It only backs up URLs in your sitemap (which is usually everything important)</li><li>Content that&apos;s loaded with JavaScript after the page loads won&apos;t be captured</li><li>Images need to be accessible when you run the backup</li><li>Very large sites might need you to adjust the parallel processing settings</li></ul><h2 id="try-it-out">Try It Out</h2><p>If you&apos;ve been looking for a simple way to backup your website or blog, give Backup Buddy a try. It&apos;s open source (MIT License), so you can use it freely and even modify it for your needs.</p><p>Check it out on GitHub: <a href="https://github.com/deanhume/backup-buddy">github.com/deanhume/backup-buddy</a></p><p>Have questions or suggestions? Feel free to open an issue or submit a pull request. Happy archiving!</p>]]></content:encoded></item><item><title><![CDATA[From Crash to Resolution: A Practical Guide for Xbox & Windows Developers]]></title><description><![CDATA[<p>If you&#x2019;ve ever had an app crash at the worst possible moment, you&#x2019;ll know how frustrating it can be - both for developers and players. Recently, I wrote an article for the Microsoft Developer blog that dives deep into this exact challenge: <strong>how to diagnose and</strong></p>]]></description><link>https://deanhume.com/from-crash-to-resolution-a-practical-guide-for-xbox-windows-developers/</link><guid isPermaLink="false">69242e41cad3b005dffc0aa4</guid><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Mon, 24 Nov 2025 10:08:19 GMT</pubDate><media:content url="https://deanhume.com/content/images/2025/11/XboxWindowsCrash_HERO.png" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2025/11/XboxWindowsCrash_HERO.png" alt="From Crash to Resolution: A Practical Guide for Xbox &amp; Windows Developers"><p>If you&#x2019;ve ever had an app crash at the worst possible moment, you&#x2019;ll know how frustrating it can be - both for developers and players. Recently, I wrote an article for the Microsoft Developer blog that dives deep into this exact challenge: <strong>how to diagnose and resolve crashes effectively on Xbox and Windows platforms</strong>.</p><p>In the post, I share practical tips, tools, and workflows that can help you move quickly and efficiently. Whether you&#x2019;re building games or apps, these techniques can improve the overall experience for your users.</p><p>&#x1F449; <a href="https://developer.microsoft.com/en-us/games/articles/2025/11/from-crash-to-resolution-practical-guide-for-xbox-windows-developers/">https://developer.microsoft.com/en-us/games/articles/2025/11/from-crash-to-resolution-practical-guide-for-xbox-windows-developers/</a></p><p>If you&#x2019;ve got any thoughts or tips of your own, I&#x2019;d love to hear them &#x2014; drop a comment or reach out!</p>]]></content:encoded></item><item><title><![CDATA[A Modern Guide to Using OAuth 2.0 with C# and Visual Studio Code]]></title><description><![CDATA[Master OAuth 2.0 in .NET 9.0! This tutorial covers using OAuth 2 to acquire tokens and access Microsoft Graph user data.]]></description><link>https://deanhume.com/a-modern-guide-to-using-oauth-2-0-with-c/</link><guid isPermaLink="false">6904cb18cad3b005dffc0a04</guid><category><![CDATA[Oauth]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Mon, 03 Nov 2025 10:45:24 GMT</pubDate><content:encoded><![CDATA[<p>Many years ago, I wrote an article on the <a href="https://deanhume.com/a-simple-guide-to-using-oauth-with-c/">basics of OAuth</a> and while it was helpful, it is now quite outdated. A <strong>lot </strong>has changed in the past 14 years! OAuth 2.0 has become the de facto standard for securing APIs and authorizing users in modern applications. </p><p>The older article I wrote used a SoundCloud example and older libraries, this updated guide walks you through implementing OAuth 2.0 in C# using Visual Studio Code, with modern libraries and best practices. </p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2025/11/Oauth_logo.svg.png" class="kg-image" alt loading="lazy" width="1196" height="1199" srcset="https://deanhume.com/content/images/size/w600/2025/11/Oauth_logo.svg.png 600w, https://deanhume.com/content/images/size/w1000/2025/11/Oauth_logo.svg.png 1000w, https://deanhume.com/content/images/2025/11/Oauth_logo.svg.png 1196w" sizes="(min-width: 720px) 720px"></figure><p>Whether you&apos;re building a console app, a web API, or integrating with services like Google or Microsoft Graph, this guide will help you get started with a clean and simple implementation.</p><hr><h3 id="prerequisites">Prerequisites</h3><p>Before we dive in, make sure you have the following:</p><ul><li><a href="https://dotnet.microsoft.com/en-us/download">https://dotnet.microsoft.com/en-us/download</a></li><li><a href="https://code.visualstudio.com/">https://code.visualstudio.com/</a></li><li>Basic understanding of HTTP and REST APIs</li><li>A registered OAuth 2.0 application (e.g., via Google, Microsoft, or a custom provider). </li></ul><p>Let&apos;s get started!</p><hr><h3 id="step-1-create-a-new-console-app">Step 1: Create a New Console App</h3><p>Open your terminal and run:</p><pre><code class="language-shell">dotnet new console -n OAuthDemo
cd OAuthDemo
</code></pre><hr><h3 id="step-2-install-required-packages">Step 2: Install Required Packages</h3><p>We&apos;ll use <code>Microsoft.Identity.Client</code> for handling OAuth flows:</p><pre><code class="language-shell">dotnet add package Microsoft.Identity.Client
dotnet add package Microsoft.Extensions.Configuration</code></pre><hr><h3 id="step-3-configure-your-oauth-settings">Step 3: Configure Your OAuth Settings</h3><p>Create a <code>appsettings.json</code> file to store your credentials:</p><pre><code class="language-json">{
  &quot;OAuth&quot;: {
    &quot;ClientId&quot;: &quot;your-client-id&quot;,
    &quot;TenantId&quot;: &quot;your-tenant-id&quot;,
    &quot;Authority&quot;: &quot;https://login.microsoftonline.com/your-tenant-id&quot;,
    &quot;Scopes&quot;: [ &quot;https://graph.microsoft.com/.default&quot; ],
    &quot;ClientSecret&quot;: &quot;your-client-secret&quot;
  }
}
</code></pre><blockquote>&#x1F50D; What is <code>tenant-id</code>? &gt;This is the unique identifier (GUID) for your Azure Active Directory (AAD) tenant. You can find it in the Azure portal under Azure Active Directory &gt; Overview. Alternatively, you can use your domain name (e.g., <code>contoso.onmicrosoft.com</code>) in place of the GUID. This might be different depending on your OAuth provider.</blockquote><hr><h3 id="step-4-authenticate-and-acquire-token">Step 4: Authenticate and Acquire Token</h3><p>Here&apos;s a simple example using MSAL to get an access token:</p><pre><code class="language-csharp">using Microsoft.Identity.Client;
using Microsoft.Extensions.Configuration;

var config = new ConfigurationBuilder()
    .AddJsonFile(&quot;appsettings.json&quot;)
    .Build();

var clientId = config[&quot;OAuth:ClientId&quot;];
var tenantId = config[&quot;OAuth:TenantId&quot;];
var authority = config[&quot;OAuth:Authority&quot;];
var scopes = config.GetSection(&quot;OAuth:Scopes&quot;).Get&lt;string[]&gt;();

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithAuthority(authority)
    .WithClientSecret(config[&quot;OAuth:ClientSecret&quot;])
    .Build();

var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
Console.WriteLine($&quot;Access Token: {result.AccessToken}&quot;);
</code></pre><hr><h3 id="step-5-make-an-authenticated-api-call">Step 5: Make an Authenticated API Call</h3><p>Use <code>HttpClient</code> to call a protected resource:</p><pre><code class="language-csharp">using System.Net.Http;
using System.Net.Http.Headers;

using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue(&quot;Bearer&quot;, result.AccessToken);

var response = await httpClient.GetAsync(&quot;https://graph.microsoft.com/v1.0/users&quot;);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
</code></pre><blockquote>&#x2705; Testing Endpoint: <code>https://graph.microsoft.com/v1.0/users</code> &gt;This endpoint returns a list of users in your Azure AD tenant. To use it, ensure your app registration has Application permissions like <code>User.Read.All</code> and that you&apos;ve granted admin consent.</blockquote><p>If all works as expected, you should see a response similar to the following:</p><pre><code class="language-csharp">{
  &quot;@odata.context&quot;: &quot;https://graph.microsoft.com/v1.0/$metadata#users&quot;,
  &quot;value&quot;: [
    {
      &quot;businessPhones&quot;: [],
      &quot;displayName&quot;: &quot;Dean Hume&quot;,
      &quot;givenName&quot;: &quot;Dean&quot;,
      &quot;jobTitle&quot;: null,
      &quot;mail&quot;: null,
      &quot;mobilePhone&quot;: null,
      &quot;officeLocation&quot;: null,
      &quot;preferredLanguage&quot;: &quot;en&quot;,
      &quot;surname&quot;: &quot;Hume&quot;,
      &quot;userPrincipalName&quot;: &quot;email.com#EXT#@email.onmicrosoft.com&quot;,
      &quot;id&quot;: &quot;id-response-goes-here&quot;
    }
  ]
}</code></pre><hr><h3 id="optional-set-up-an-entra-id-test-oauth-app">*Optional: Set up an Entra ID test OAuth App</h3><p>This is an optional step as you might be wanting to use your own OAuth provider, however, I wanted to be sure that my code worked and I set up an App in <a href="https://www.microsoft.com/en-gb/security/business/identity-access/microsoft-entra-id">Azure&apos;s Entra ID</a> that I could test with. </p><p>If you&apos;d like to do the same, I recommend checking out these resources:</p><ul><li><a href="https://learn.microsoft.com/en-us/entra/fundamentals/create-new-tenant">Quickstart: Create a new tenant in Microsoft Entra ID</a></li><li><a href="https://www.spletzer.com/2025/01/how-to-get-your-client-id-and-client-secret-from-entra-id/">Client Id &amp; Secret from Entra ID</a></li></ul><h3 id="conclusion">Conclusion</h3><p>OAuth 2.0 doesn&apos;t have to be intimidating. With modern libraries like MSAL and tools like Visual Studio Code, you can securely authenticate and access APIs with just a few lines of code. </p><p>If you&apos;d like to see an example of this code in action, please head over to the <a href="https://github.com/deanhume/oauthdemo">Github repo</a> that I created to accompany this article.</p>]]></content:encoded></item><item><title><![CDATA[HTML Minifier: A 12-Year Journey Building and Maintaining an Open Source Tool]]></title><description><![CDATA[Discover the 12-year journey behind HTML Minifier an open source tool that helped developers optimize web performance and reduce page load times.]]></description><link>https://deanhume.com/html-minifier-a-12-year-journey-building-and-maintaining-an-open-source-tool/</link><guid isPermaLink="false">690357decad3b005dffc098c</guid><category><![CDATA[Web Performance]]></category><category><![CDATA[HTML]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Thu, 30 Oct 2025 14:49:50 GMT</pubDate><media:content url="https://deanhume.com/content/images/2025/10/html-minifier-header.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2025/10/html-minifier-header.jpg" alt="HTML Minifier: A 12-Year Journey Building and Maintaining an Open Source Tool"><p>Back in 2013, I published a blog post about a simple <a href="https://deanhume.com/a-simple-html-minifier-for-asp-net/">HTML Minifier</a> I&apos;d built for web pages. At the time, web performance was becoming increasingly important, and I needed a straightforward way to reduce the size of HTML files in my projects. What started as a personal solution has evolved into a tool that&apos;s been used by developers worldwide for over a decade. Today, I want to reflect on that journey and share what the tool has become.</p><h2 id="the-problem-then-and-now">The Problem (Then and Now)</h2><p>Web performance has always been critical, but it&apos;s even more important today. Every KB matters when you&apos;re trying to deliver fast experiences to users across the globe on varying network conditions. While we have sophisticated build tools and bundlers now, the core principle remains the same: <strong>smaller files load faster</strong>.</p><p>HTML minification removes unnecessary whitespace, line breaks, and comments from your HTML files - things that make code readable for humans but add unnecessary bytes for browsers. When you&apos;re shipping production code, these extras are just wasted bandwidth.</p><h2 id="what-started-as-a-simple-tool">What Started as a Simple Tool</h2><p>The original HTML Minifier was straightforward: point it at a folder of HTML or ASP.NET Razor views, and it would strip out the unnecessary characters. It was born from a real need I had while working on ASP.NET projects where manually minifying views or integrating complex build processes wasn&apos;t practical.</p><pre><code class="language-bash">HtmlMinifier.exe &quot;C:\MyProject\Views&quot;
</code></pre><p>That was it. Simple, effective, and it got the job done.</p><h2 id="evolution-over-12-years">Evolution Over 12 Years</h2><p>What&apos;s been fascinating is watching the tool evolve based on real-world usage and community feedback. Here are some of the key improvements that have shaped the project:</p><h3 id="framework-support">Framework Support</h3><p>Early on, I learned that aggressive minification could break certain JavaScript frameworks. Knockout.js, for example, uses HTML comments for control flow. Angular had similar patterns. This led to adding configuration flags:</p><pre><code class="language-bash"># Preserve HTML comments for frameworks that need them
HtmlMinifier.exe &quot;C:\Folder&quot; ignorehtmlcomments

# Keep Knockout.js comments specifically
HtmlMinifier.exe &quot;C:\Folder&quot; ignoreknockoutcomments
</code></pre><p>This was a great lesson in understanding your users&apos; workflows. A tool is only useful if it fits into real-world scenarios.</p><h3 id="performance-improvements">Performance Improvements</h3><p>As the tool gained users, performance became more important. People weren&apos;t just minifying a handful of files - they were processing entire web applications with thousands of views. I added:</p><ul><li><strong>Recursive directory scanning</strong> for processing entire project structures</li><li><strong>Multi-file support</strong> for targeted minification</li><li><strong>Line length controls</strong> to prevent overly long lines that could impact certain text editors or source control systems</li><li><strong>Performance metrics</strong> showing bytes saved and processing time</li></ul><pre><code class="language-bash"># Limit lines to 60,000 characters
HtmlMinifier.exe &quot;C:\Folder&quot; &quot;60000&quot;
</code></pre><h3 id="modern-development-practices">Modern Development Practices</h3><p>The .NET ecosystem has changed dramatically since 2013. The project has evolved alongside it:</p><ul><li><strong>Continuous Integration</strong>: GitHub Actions now automatically build and test every commit</li><li><strong>Comprehensive Testing</strong>: Over 60 unit tests covering edge cases, performance benchmarks, and real-world scenarios</li><li><strong>Better Error Handling</strong>: Proper validation, informative error messages, and graceful failure modes</li><li><strong>Cross-framework Support</strong>: HTML, Razor views (.cshtml), and Web Forms (.aspx)</li></ul><h3 id="community-contributions">Community Contributions</h3><p>One of the most rewarding parts of this journey has been the incredible support from the community. Over the years, twelve different contributors have jumped in to help improve the tool - surfacing bugs and edge cases I hadn&#x2019;t considered, submitting fixes for real-world production issues, adding support for specific frameworks and use cases, and even enhancing the documentation and usage examples. </p><p>Every pull request, issue, and suggestion has helped shape the tool into something better.</p><h3 id="performance-gains">Performance Gains</h3><p>The tool typically achieves:</p><ul><li><strong>10-40% size reduction</strong> for typical HTML files</li><li><strong>20-50% reduction</strong> for files with heavy indentation and whitespace</li><li><strong>Processing speeds</strong> that can handle thousands of files in seconds</li></ul><p>For a production website with hundreds of views, this translates to meaningful bandwidth savings and faster page loads.</p><h2 id="lessons-from-maintaining-an-open-source-tool">Lessons from Maintaining an Open Source Tool</h2><h3 id="1-simplicity-is-a-feature">1. <strong>Simplicity is a Feature</strong></h3><p>In an era of complex build toolchains, there&apos;s still value in tools that do one thing well. The HTML Minifier has survived because it&apos;s simple to understand and use. You don&apos;t need to configure Webpack, install npm packages, or learn a new build system - you just run it.</p><p>But I&apos;m also mindful that sometimes the best feature is knowing what <em>not</em> to add. The tool&apos;s simplicity is part of its appeal.</p><h3 id="2-real-users-reveal-real-problems">2. <strong>Real Users Reveal Real Problems</strong></h3><p>Some of the most impactful improvements came directly from folks using the tool in production. They uncovered edge cases I hadn&#x2019;t even considered - like deeply nested HTML structures, Unicode and international character handling, quirky Razor syntax combinations, and performance bottlenecks with massive files. Their feedback helped push the tool to be more robust and production-ready.</p><h3 id="3-open-source-is-a-marathon">3. <strong>Open Source is a Marathon</strong></h3><p>Twelve years is a long time. Technologies come and go, but useful tools persist. The key is:</p><ul><li>Responding to issues thoughtfully</li><li>Being conservative with breaking changes</li><li>Keeping the tool focused on its core purpose</li><li>Appreciating and crediting contributors</li></ul><h2 id="try-it-yourself">Try It Yourself</h2><p>If you&apos;re working on a web project and need a straightforward way to minify HTML, give it a try:</p><p><strong>GitHub</strong>: <a href="https://github.com/deanhume/html-minifier">https://github.com/deanhume/html-minifier</a></p><h3 id="reflections">Reflections</h3><p>Looking back at 12 years of maintaining this tool has been surprisingly rewarding. It&apos;s been used in projects I&apos;ll never know about, solved problems I didn&apos;t anticipate, and connected me with developers around the world.</p><p>The web development landscape has changed dramatically since 2013:</p><ul><li>We&apos;ve gone from jQuery to React, Vue, and beyond</li><li>Build tools have become incredibly sophisticated</li><li>Web performance tooling has advanced tremendously</li><li>The rise of SPAs, PWAs, and modern frameworks</li></ul><p>Yet through all these changes, the need for efficient HTML delivery remains constant. That&apos;s perhaps the most interesting lesson: <strong>fundamental problems persist even as technologies evolve</strong>.</p><h2 id="thank-you">Thank You</h2><p>To everyone who&apos;s used the tool, filed an issue, contributed code, or simply found it helpful - thank you. Open source thrives on community, and this project has been a small but meaningful part of my contribution to that ecosystem.</p><p>Here&apos;s to another 12 years of making the web a little bit faster, one minified file at a time. &#x1F680;</p><hr><p><strong>Resources</strong>:</p><ul><li><a href="https://github.com/deanhume/html-minifier">GitHub Repository</a></li><li><a href="https://deanhume.com/a-simple-html-minifier-for-asp-net/">Original Blog Post (2013)</a></li><li><a href="https://github.com/deanhume/html-minifier/releases">Latest Release</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Best Practices for Handheld Gaming Development]]></title><description><![CDATA[<p>If you&apos;ve ever played a game on a modern handheld device, you&apos;ll know how much fun it can be. I recently wrote an article on the Microsoft Game Dev Blog about some key recommendations to help you craft exceptional gaming experiences for handheld devices.</p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2025/06/Handheld-Device-Asus-Rog-Xbox.png" class="kg-image" alt="Handheld Device -Asus Xbox Rog Ally" loading="lazy" width="1920" height="1080" srcset="https://deanhume.com/content/images/size/w600/2025/06/Handheld-Device-Asus-Rog-Xbox.png 600w, https://deanhume.com/content/images/size/w1000/2025/06/Handheld-Device-Asus-Rog-Xbox.png 1000w, https://deanhume.com/content/images/size/w1600/2025/06/Handheld-Device-Asus-Rog-Xbox.png 1600w, https://deanhume.com/content/images/2025/06/Handheld-Device-Asus-Rog-Xbox.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>Please head</p>]]></description><link>https://deanhume.com/best-practices-for-handheld-gaming-development/</link><guid isPermaLink="false">684fe789cad3b005dffc094a</guid><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Mon, 16 Jun 2025 09:47:47 GMT</pubDate><content:encoded><![CDATA[<p>If you&apos;ve ever played a game on a modern handheld device, you&apos;ll know how much fun it can be. I recently wrote an article on the Microsoft Game Dev Blog about some key recommendations to help you craft exceptional gaming experiences for handheld devices.</p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2025/06/Handheld-Device-Asus-Rog-Xbox.png" class="kg-image" alt="Handheld Device -Asus Xbox Rog Ally" loading="lazy" width="1920" height="1080" srcset="https://deanhume.com/content/images/size/w600/2025/06/Handheld-Device-Asus-Rog-Xbox.png 600w, https://deanhume.com/content/images/size/w1000/2025/06/Handheld-Device-Asus-Rog-Xbox.png 1000w, https://deanhume.com/content/images/size/w1600/2025/06/Handheld-Device-Asus-Rog-Xbox.png 1600w, https://deanhume.com/content/images/2025/06/Handheld-Device-Asus-Rog-Xbox.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>Please head over to <a href="https://developer.microsoft.com/en-us/games/articles/2025/06/best-practices-for-handheld-gaming-development/">this link</a> to find out more info.</p>]]></content:encoded></item><item><title><![CDATA[Getting started with Azure's Hybrid and Embedded Text-to-Speech]]></title><description><![CDATA[<p>Over the past few months, I&apos;ve been experimenting with Azure&apos;s Text-to-Speech service. It is a super powerful API that enables fluid, natural-sounding text to speech that matches the tone and emotion of human voices.</p><p>Whether you are building an app or a game, Text-to-Speech can be</p>]]></description><link>https://deanhume.com/azure-hybrid-and-embedded-text-to-speech/</link><guid isPermaLink="false">6582ad95c919137deeb7a806</guid><category><![CDATA[Text-to-Speech]]></category><category><![CDATA[AI]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Thu, 15 Feb 2024 09:41:58 GMT</pubDate><content:encoded><![CDATA[<p>Over the past few months, I&apos;ve been experimenting with Azure&apos;s Text-to-Speech service. It is a super powerful API that enables fluid, natural-sounding text to speech that matches the tone and emotion of human voices.</p><p>Whether you are building an app or a game, Text-to-Speech can be very useful. For example, think of the different stages of game development - during concept and pre-production, text-to-speech can help build out the feel of the game and enhance your scripts before you record with real voice actors. </p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2024/02/text-to-speech-game-development.jpg" class="kg-image" alt loading="lazy" width="1000" height="516" srcset="https://deanhume.com/content/images/size/w600/2024/02/text-to-speech-game-development.jpg 600w, https://deanhume.com/content/images/2024/02/text-to-speech-game-development.jpg 1000w" sizes="(min-width: 720px) 720px"></figure><p>During release and production , it can be used to provide accessibility options to suit the needs of your users. &#xA0;At the time of writing this article, there are over 456 voices across 147 languages that you can choose from! </p><p>As you are reading through this, you might be thinking to yourself...hang on...This uses a Cloud service, how would this work for an offline game? Or when a user loses connection?</p><p>This is where Hybrid (and Embedded) speech comes into play, and in this article, we are going to explore an example that will work both online and offline scenarios.</p><h2 id="how-hybrid-speech-works">How Hybrid Speech works</h2><p>Hybrid speech uses the cloud speech service by default and embedded speech as a fallback in case cloud connectivity is limited or slow.</p><p>In fact, you could ship your entire app with just the embedded speech and not use the cloud service at all. Its worth mentioning that it is slightly limited in that while the quality is good, the cloud option returns the highest quality speech. To give you and idea of what this looks like, lets compare the two versions. The first is the embedded speech version:</p><div class="kg-card kg-audio-card"><img src alt="audio-thumbnail" class="kg-audio-thumbnail kg-audio-hide"><div class="kg-audio-thumbnail placeholder"><svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 15.33a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm-2.25.75a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM15 13.83a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm-2.25.75a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M14.486 6.81A2.25 2.25 0 0 1 17.25 9v5.579a.75.75 0 0 1-1.5 0v-5.58a.75.75 0 0 0-.932-.727.755.755 0 0 1-.059.013l-4.465.744a.75.75 0 0 0-.544.72v6.33a.75.75 0 0 1-1.5 0v-6.33a2.25 2.25 0 0 1 1.763-2.194l4.473-.746Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M3 1.5a.75.75 0 0 0-.75.75v19.5a.75.75 0 0 0 .75.75h18a.75.75 0 0 0 .75-.75V5.133a.75.75 0 0 0-.225-.535l-.002-.002-3-2.883A.75.75 0 0 0 18 1.5H3ZM1.409.659A2.25 2.25 0 0 1 3 0h15a2.25 2.25 0 0 1 1.568.637l.003.002 3 2.883a2.25 2.25 0 0 1 .679 1.61V21.75A2.25 2.25 0 0 1 21 24H3a2.25 2.25 0 0 1-2.25-2.25V2.25c0-.597.237-1.169.659-1.591Z"/></svg></div><div class="kg-audio-player-container"><audio src="https://deanhume.com/content/media/2024/02/device-22.wav" preload="metadata"></audio><div class="kg-audio-title">Embedded Text to Speech</div><div class="kg-audio-player"><button class="kg-audio-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-audio-pause-icon kg-audio-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-audio-current-time">0:00</span><div class="kg-audio-time">/<span class="kg-audio-duration">0:02</span></div><input type="range" class="kg-audio-seek-slider" max="100" value="0"><button class="kg-audio-playback-rate">1&#xD7;</button><button class="kg-audio-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-audio-mute-icon kg-audio-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-audio-volume-slider" max="100" value="100"></div></div></div><p>And the the second is the cloud based version:</p><div class="kg-card kg-audio-card"><img src alt="audio-thumbnail" class="kg-audio-thumbnail kg-audio-hide"><div class="kg-audio-thumbnail placeholder"><svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 15.33a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm-2.25.75a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM15 13.83a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm-2.25.75a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M14.486 6.81A2.25 2.25 0 0 1 17.25 9v5.579a.75.75 0 0 1-1.5 0v-5.58a.75.75 0 0 0-.932-.727.755.755 0 0 1-.059.013l-4.465.744a.75.75 0 0 0-.544.72v6.33a.75.75 0 0 1-1.5 0v-6.33a2.25 2.25 0 0 1 1.763-2.194l4.473-.746Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M3 1.5a.75.75 0 0 0-.75.75v19.5a.75.75 0 0 0 .75.75h18a.75.75 0 0 0 .75-.75V5.133a.75.75 0 0 0-.225-.535l-.002-.002-3-2.883A.75.75 0 0 0 18 1.5H3ZM1.409.659A2.25 2.25 0 0 1 3 0h15a2.25 2.25 0 0 1 1.568.637l.003.002 3 2.883a2.25 2.25 0 0 1 .679 1.61V21.75A2.25 2.25 0 0 1 21 24H3a2.25 2.25 0 0 1-2.25-2.25V2.25c0-.597.237-1.169.659-1.591Z"/></svg></div><div class="kg-audio-player-container"><audio src="https://deanhume.com/content/media/2024/02/cloud-22.wav" preload="metadata"></audio><div class="kg-audio-title">Cloud Text to Speech</div><div class="kg-audio-player"><button class="kg-audio-play-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"/></svg></button><button class="kg-audio-pause-icon kg-audio-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"/><rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"/></svg></button><span class="kg-audio-current-time">0:00</span><div class="kg-audio-time">/<span class="kg-audio-duration">0:02</span></div><input type="range" class="kg-audio-seek-slider" max="100" value="0"><button class="kg-audio-playback-rate">1&#xD7;</button><button class="kg-audio-unmute-icon"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"/></svg></button><button class="kg-audio-mute-icon kg-audio-hide"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"/></svg></button><input type="range" class="kg-audio-volume-slider" max="100" value="100"></div></div></div><p>If you listen really closely, you can hear that there is a slight improvement in the tone and cadence of the cloud speech service. There is not much difference, and the embedded version sounds pretty good too!</p><p>In this article, I am going to take you through a basic example of hybrid and embedded speech using Azure&apos;s Text to Speech Service.</p><h2 id="lets-get-started">Let&apos;s get started</h2><p>Before we get started, we need to download the voices that we will use with the embedded version of the code. That is, the voices that will actually &quot;ship&quot; with the code. In order to acquire the voices, you will need to apply for access - follow <a href="https://aka.ms/csgate-embedded-speech">this link to request access</a> to the voices. </p><p>Once you have the voices, we can then start building out our example. First off, let&apos;s start by creating a <a href="https://learn.microsoft.com/en-us/visualstudio/ide/create-new-project?view=vs-2022">new project in Visual Studio Code</a>. Next, add a new class called <em>Keys </em>that will contain the keys and settings that we need.</p><!--kg-card-begin: markdown--><pre><code>public class Keys
{
    public static string EmbeddedSpeechSynthesisVoicePath = @&quot;\voices\en-us&quot;; 
    public static string EmbeddedSpeechSynthesisVoiceKey = &quot;your_key&quot;; 
    public static string EmbeddedSpeechSynthesisVoiceName = &quot;en-US-JennyNeural&quot;; 
    
    public static string CloudSpeechSubscriptionKey = &quot;subscription_key&quot;; 
    public static string CloudSpeechServiceRegion = &quot;eastus&quot;; 
    public static string SpeechRecognitionLocale = &quot;en-US&quot;; 
    public static string SpeechSynthesisLocale = &quot;en-US&quot;; 
}
</code></pre>
<!--kg-card-end: markdown--><p>Let&apos;s break down the code above. Firstly, the variable <strong>EmbeddedSpeechSynthesisVoicePath </strong>points to the file location where the voices are located and <strong>EmbeddedSpeechSynthesisVoiceKey</strong> is the key that you need to access the voices. You&apos;ll be given these when you apply for the access as mentioned above. I&apos;ve also chosen the voice of &quot;Jenny&quot;, but you could choose any from the <a href="https://speech.microsoft.com/portal/voicegallery">Voice Gallery</a>.</p><p>As we are using a hybrid model, we&apos;ll need to provide some cloud details from an Azure speech instance. I created a new speech instance on the Azure portal and on the overview page, I selected the <strong>CloudSpeechSubscriptionKey</strong> and <strong>CloudSpeechServiceRegion </strong>from the portal (highlighted in yellow below).</p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2024/02/azure-portal-text-to-speech.jpg" class="kg-image" alt loading="lazy" width="1200" height="1190" srcset="https://deanhume.com/content/images/size/w600/2024/02/azure-portal-text-to-speech.jpg 600w, https://deanhume.com/content/images/size/w1000/2024/02/azure-portal-text-to-speech.jpg 1000w, https://deanhume.com/content/images/2024/02/azure-portal-text-to-speech.jpg 1200w" sizes="(min-width: 720px) 720px"></figure><p>In the code above, we are also providing the instance region and the locale that we are going to be using in the variables <strong>SpeechRecognitionLocale </strong>and <strong>SpeechSynthesisLocale</strong>. In this case I&apos;m using US English, but you could use any language and locale of your choice.</p><h2 id="configuring-the-embedded-speech">Configuring the Embedded Speech</h2><p>Now that we have the keys configured, we can create the config for the embedded speech model. I&apos;ve created a new class file called <strong>Settings </strong>and added the following code.</p><!--kg-card-begin: markdown--><pre><code>public class Settings
{

public static EmbeddedSpeechConfig CreateEmbeddedSpeechConfig()
{
    List&lt;string&gt; paths = new List&lt;string&gt;();

    var synthesisVoicePath = Keys.EmbeddedSpeechSynthesisVoicePath;
    if (!string.IsNullOrEmpty(synthesisVoicePath) &amp;&amp; !synthesisVoicePath.Equals(&quot;YourEmbeddedSpeechSynthesisVoicePath&quot;))
    {
        paths.Add(synthesisVoicePath);
    }

    // Make sure that there is a voice path defined above.
    if (paths.Count == 0)
    {
        Console.Error.WriteLine(&quot;## ERROR: No model path(s) specified.&quot;);
        return null;
    }

    var config = EmbeddedSpeechConfig.FromPaths(paths.ToArray());

    if (!string.IsNullOrEmpty(Keys.EmbeddedSpeechSynthesisVoiceName))
    {
        // Mandatory configuration for embedded speech synthesis.
        config.SetSpeechSynthesisVoice(Keys.EmbeddedSpeechSynthesisVoiceName, Keys.EmbeddedSpeechSynthesisVoiceKey);
        if (Keys.EmbeddedSpeechSynthesisVoiceName.Contains(&quot;Neural&quot;))
        {
            // Embedded neural voices only support 24kHz sample rate.
config.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm);
        }
    }

    return config;
}
</code></pre>
<!--kg-card-end: markdown--><p>The code above uses the different file paths and keys that we set in the Keys class file. We&apos;ll be using this <strong>EmbeddedSpeechConfig </strong>object to create speech using the local voices on file.</p><h2 id="configuring-the-hybrid-speech">Configuring the Hybrid Speech</h2><p>In the same way that we set up the <strong>EmbeddedSpeechConfig </strong>object, we&apos;ll need to create a <strong>CreateHybridSpeechConfig </strong>object. </p><!--kg-card-begin: markdown--><pre><code>public static HybridSpeechConfig CreateHybridSpeechConfig()
{
    var cloudSpeechConfig = SpeechConfig.FromSubscription(Keys.CloudSpeechSubscriptionKey, Keys.CloudSpeechServiceRegion);

    cloudSpeechConfig.SpeechRecognitionLanguage = Keys.SpeechRecognitionLocale;
    cloudSpeechConfig.SpeechSynthesisLanguage = Keys.SpeechSynthesisLocale;

    var embeddedSpeechConfig = CreateEmbeddedSpeechConfig();

    var config = HybridSpeechConfig.FromConfigs(cloudSpeechConfig, embeddedSpeechConfig);

    return config;
}
</code></pre>
<!--kg-card-end: markdown--><p>You&apos;ll notice that the code above calls the <em>CreateEmbeddedSpeechConfig()</em> function that we created earlier. This is because we&apos;ll be using this code to use the cloud speech service by default and then fallback to the embedded speech in case cloud connectivity is limited or slow. </p><h2 id="try-it-out">Try it out</h2><p>With all this in place, we are now ready to start calling the API and synthesizing some speech. </p><!--kg-card-begin: markdown--><pre><code>/// &lt;summary&gt;
/// Synthesizes speech using the hybrid speech system and outputs it to the default speaker.
/// &lt;/summary&gt;
private static async Task HybridSynthesisToSpeaker()
{
    var textToSpeak = &quot;Hello, this is a test of the hybrid speech system.&quot;;

    var speechConfig = Settings.CreateHybridSpeechConfig();

    using var audioConfig = AudioConfig.FromDefaultSpeakerOutput();

    using var synthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
    
    using var result = await synthesizer.SpeakTextAsync(textToSpeak);
}
</code></pre>
<!--kg-card-end: markdown--><p>When you call <em>HybridSynthesisToSpeaker().Wait()</em>, you should now hear something coming from your speaker!</p><p>In these code samples, I have created a simple string that we are passing through to the API. Depending on your use case, you could build more complex examples using <a href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-synthesis-markup">Speech Synthesis Markup Language (SSML)</a>. Speech Synthesis Markup Language (SSML) is an XML-based markup language that you can use to fine-tune your text to speech output attributes such as pitch, pronunciation, speaking rate, volume, and more. </p><h2 id="using-embedded-speech-only">Using Embedded Speech only</h2><p>If you preferred to use embedded speech only, you can call the <em>CreateEmbeddedSpeechConfig() </em>function that we created earlier.</p><!--kg-card-begin: markdown--><pre><code>/// &lt;summary&gt;
/// Synthesizes speech using the embedded speech system and outputs it to the default speaker.
/// &lt;/summary&gt;
private static async Task EmbeddedSynthesisToSpeaker()
{
    var textToSpeak = &quot;Hello, this is a test of the embedded speech system.&quot;;

    var speechConfig = Settings.CreateEmbeddedSpeechConfig();
    
    using var audioConfig = AudioConfig.FromDefaultSpeakerOutput();

    using var synthesizer = new SpeechSynthesizer(speechConfig, audioConfig);

    using var result = await synthesizer.SpeakTextAsync(textToSpeak);
}
</code></pre>
<!--kg-card-end: markdown--><p>When you call <em>EmbeddedSynthesisToSpeaker().Wait()</em>, you should now hear embedded speech coming from your speaker!</p><h2 id="summary">Summary</h2><p>I&apos;ve barely scratched the surface of the capabilities of text-to-speech; there is so much more to experiment with! If you&apos;d like to learn more about Embedded/Hybrid Speech, I recommend reading the <a href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/embedded-speech?tabs=windows-target%2Cjre&amp;pivots=programming-language-csharp">following article</a> for more information.</p><p>In this article, we covered both embedded and hybrid speech options and its also worth mentioning that you can ship with only embedded speech if you prefer. In the code example that we ran through, we used C# but there are other language options available including C++, and Java SDKs.</p>]]></content:encoded></item><item><title><![CDATA[Azure Function Timer Trigger not firing - NCrontab]]></title><description><![CDATA[<p>If you&apos;ve recently ventured into the world of <a href="https://azure.microsoft.com/en-gb/products/functions/">Azure Functions</a>, you&apos;re likely familiar with the versatility they offer when it comes to scheduling tasks. In this guide, we&apos;ll delve into using <a href="https://learn.microsoft.com/en-gb/azure/azure-functions/functions-create-scheduled-function">Azure Functions&apos; Time Trigger</a> function with Node.js and Visual Studio Code,</p>]]></description><link>https://deanhume.com/azure-http-trigger/</link><guid isPermaLink="false">651d2c1ec919137deeb7a735</guid><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Wed, 04 Oct 2023 11:03:57 GMT</pubDate><media:content url="https://deanhume.com/content/images/2023/10/azure-function-timer-trigger.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2023/10/azure-function-timer-trigger.jpg" alt="Azure Function Timer Trigger not firing - NCrontab"><p>If you&apos;ve recently ventured into the world of <a href="https://azure.microsoft.com/en-gb/products/functions/">Azure Functions</a>, you&apos;re likely familiar with the versatility they offer when it comes to scheduling tasks. In this guide, we&apos;ll delve into using <a href="https://learn.microsoft.com/en-gb/azure/azure-functions/functions-create-scheduled-function">Azure Functions&apos; Time Trigger</a> function with Node.js and Visual Studio Code, uncovering some important nuances along the way.</p><p>This article assumes that you have some familiarity with Node.js and a basic understanding of Azure.</p><h3 id="getting-started">Getting Started</h3><p>To begin, you&apos;ll need Node.js and Visual Studio Code installed, along with the Azure Functions extension. When you create a new function, it comes with some boilerplate code that includes a schedule property defining when the function should run. By default, it&apos;s set to run every 5 minutes.</p><!--kg-card-begin: markdown--><pre><code>const { app } = require(&apos;@azure/functions&apos;);

app.timer(&apos;timerTrigger2&apos;, {
    schedule: &apos;0 */5 * * * *&apos;,
    handler: (myTimer, context) =&gt; {
        context.log(&apos;Timer function processed request.&apos;);
    }
});
</code></pre>
<!--kg-card-end: markdown--><p>The <strong>schedule parameter</strong> in the above code tells the Azure Function how often to fire.</p><h3 id="the-challenge">The Challenge</h3><p>But what if you want your function to run every 11 hours during a day? Crafting the right Cron expression can be tricky, and that&apos;s where I encountered some difficulties. I turned to an online Cron expression generator, hoping it would simplify the process. Here&apos;s what it gave me:</p><!--kg-card-begin: markdown--><pre><code>0 0 */11 * *
</code></pre>
<!--kg-card-end: markdown--><p>Feeling confident, I integrated this expression into my code above and deployed the Azure Function. However, it didn&apos;t work as expected. I scoured the logs for clues but found nothing. To troubleshoot and debug locally, I temporarily changed the Cron expression to run every minute, and it worked flawlessly. So why wasn&apos;t it firing every few hours?</p><h3 id="the-revelation">The Revelation</h3><p>After some online research, I uncovered the reason behind the issue. Azure Functions use an <strong>NCRONTAB expression</strong>, which requires a six-part format instead of the traditional five-part Cron expression. The online generator that I found online had provided a five-part expression, leading to the problem.</p><p>To be fair, the Visual Studio Code extension for Azure Functions provides the correct six-part format by default. My mistake was changing it using a five-part expression from an online tool - d&apos;oh!</p><h3 id="the-solution">The Solution</h3><p>To rectify this, I recommend using the <a href="https://ncrontab.swimburger.net/">NCrontab Expression Tester</a>. This user-friendly tool not only helps you test your expressions but also generates the correct six-part Cron expressions tailored for Azure Functions. </p><p>When I updated my code to use the 6 part format:</p><!--kg-card-begin: markdown--><pre><code>0 0 */11 * * *
</code></pre>
<!--kg-card-end: markdown--><p>It ran perfectly!</p><h3 id="why-ncrontab">Why NCrontab?</h3><p>You might be wondering why Azure Functions use the NCrontab six-part format instead of the traditional five-part format. The answer lies in its flexibility. The six-part format allows you to specify seconds, enabling you to run your functions with higher precision and frequency.</p><!--kg-card-begin: markdown--><pre><code>* * * * * *
- - - - - -
| | | | | |
| | | | | +--- day of week (0 - 6) (Sunday=0)
| | | | +----- month (1 - 12)
| | | +------- day of month (1 - 31)
| | +--------- hour (0 - 23)
| +----------- min (0 - 59)
+------------- sec (0 - 59)
</code></pre>
<!--kg-card-end: markdown--><p>For further details on NCrontab expressions, you can visit the GitHub repository <a href="https://github.com/atifaziz/NCrontab">here</a>.</p><p>Happy coding!</p>]]></content:encoded></item><item><title><![CDATA[Using a Raspberry Pi to track the progress of your homebrew]]></title><description><![CDATA[In this article I go into detail about how I hooked up a Raspberry to continually track and monitor the progress of my beer all whilst graphing it out to another application. It gets a little geeky!]]></description><link>https://deanhume.com/using-a-raspberry-pi-to-track-the-progress-of-your-homebrew/</link><guid isPermaLink="false">6419d57e92605e0b555b234f</guid><category><![CDATA[Charts]]></category><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Thu, 18 Aug 2022 10:48:32 GMT</pubDate><media:content url="https://deanhume.com/content/images/2023/10/raspberry-pi-homebrew.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2023/10/raspberry-pi-homebrew.jpg" alt="Using a Raspberry Pi to track the progress of your homebrew"><p>In my spare time, I like to do a bit of <a href="https://humebrew.com">homebrewing</a>. While it isn&apos;t the usual sort of topic that I post on this site, I wanted to cross post to an article I wrote about automating some of the brewing process.</p><p>If you&apos;ve ever goofed around with Raspberry Pi&apos;s before, you&apos;ll know how fun they can be. <a href="https://humebrew.com/tilt-hydrometer-to-a-raspberry-pi-to-brewfather/">In this article</a> I go into detail about how I hooked up a Raspberry Pi to continually track and monitor the progress of my beer all whilst graphing it out to another application. It gets a little geeky!</p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2022/08/raspberry-pi-tilt-pi.jpg" class="kg-image" alt="Using a Raspberry Pi to track the progress of your homebrew" loading="lazy"></figure><p>Please head over to my homebrew website over at <a href="https://humebrew.com/tilt-hydrometer-to-a-raspberry-pi-to-brewfather/">Humebrew.com</a> to find out more!</p>]]></content:encoded></item><item><title><![CDATA[Has Service Worker usage increased on this website over the past four years?]]></title><description><![CDATA[It's been almost four years since I started collecting data on Service Worker usage and support on this website and in this article I show the details.]]></description><link>https://deanhume.com/service-worker-growth-over-time/</link><guid isPermaLink="false">6419d57e92605e0b555b234a</guid><category><![CDATA[Service Workers]]></category><category><![CDATA[Progressive Web Apps]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Fri, 01 Apr 2022 08:20:39 GMT</pubDate><media:content url="https://deanhume.com/content/images/2023/10/service-worker-usage.png" medium="image"/><content:encoded><![CDATA[<img src="https://deanhume.com/content/images/2023/10/service-worker-usage.png" alt="Has Service Worker usage increased on this website over the past four years?"><p>I think <a href="https://deanhume.com/progressive-web-apps-my-new-book-is-available/">Service Workers are pretty cool</a>. They enable us to do so many powerful things with the web today such as intercepting network requests, caching and retrieving resources and delivering push messages to name a few. In fact, I&apos;ve been running a Service Worker file on this website for around the past four years. </p><p>A few years ago, I wrote an article that showed how to determine the <a href="https://deanhume.com/determining-service-worker-support/">Service Worker usage and support</a> for the users that visited your site by pushing statistics to Google Analytics. &#xA0;It also went a step further and determined if the users were being served cached data through the supported service worker.</p><p>It&apos;s been almost four years since I started collecting data and I wanted to look back over time and see if there has been any change to the numbers. Let&apos;s first start with the overall number - the chart below represents the percentage of users that visit this website that have browsers that support Service Workers. This number includes desktop and all devices (mobiles, tablets).</p><!--kg-card-begin: html--><div class="infogram-embed" data-id="5316af28-d9af-4bf7-8822-bbff52acf4a8" data-type="interactive" data-title="Column Chart"></div><script>!function(e,i,n,s){var t="InfogramEmbeds",d=e.getElementsByTagName("script")[0];if(window[t]&&window[t].initialized)window[t].process&&window[t].process();else if(!e.getElementById(n)){var o=e.createElement("script");o.async=1,o.id=n,o.src="https://e.infogram.com/js/dist/embed-loader-min.js",d.parentNode.insertBefore(o,d)}}(document,0,"infogram-async");</script><div style="padding:8px 0;font-family:Arial!important;font-size:13px!important;line-height:15px!important;text-align:center;border-top:1px solid #dadada;margin:0 30px"><br></div><!--kg-card-end: html--><p>From the chart above, you can see that between 2019 and 2021 the percentage of users has remained relatively consistent. The numbers for 2022 have jumped considerably, but it&apos;s worth pointing out that at the time of writing this article, we are only 3 months into this year. I expect that this number will flatten out over time.</p><p>If you click on the second tab in the chart above, you&apos;ll notice the number of &quot;controlled&quot; users to the site. In this case it means how many pages were actually <em>controlled </em>by an active service worker. For example, if someone visits a site with a service worker on it, it will get installed the first time and only used the next time they reload the page or navigate away. I wanted to determine how many people were both supported and using my service worker.</p><p>You can see from these numbers that over the years it has remained pretty stable around the 25% mark.</p><p>As I looked at the data over time, it was clear that the desktop usage has remained pretty stable with very little growth. I wanted to dive a little more into the mobile phone usage on this site and the chart below represents these numbers.</p><!--kg-card-begin: html--><div class="infogram-embed" data-id="358b8eb9-1d25-4da6-9641-9cb8b282a60d" data-type="interactive" data-title="Mobile: Total Service Worker Usage"></div><script>!function(e,i,n,s){var t="InfogramEmbeds",d=e.getElementsByTagName("script")[0];if(window[t]&&window[t].initialized)window[t].process&&window[t].process();else if(!e.getElementById(n)){var o=e.createElement("script");o.async=1,o.id=n,o.src="https://e.infogram.com/js/dist/embed-loader-min.js",d.parentNode.insertBefore(o,d)}}(document,0,"infogram-async");</script><div style="padding:8px 0;font-family:Arial!important;font-size:13px!important;line-height:15px!important;text-align:center;border-top:1px solid #dadada;margin:0 30px"></div><!--kg-card-end: html--><p>As you can see from the chart above, the number of mobile devices that visit this site that support Service Workers has remained pretty stable. There doesn&apos;t seem like much of a story to tell here. While 2022 looks like it has increased a bit, at the time of writing this article, we are only 3 months into this year. I expect these numbers to flatten out.</p><p>Of all the data that I managed to collect, I only started to notice a trend when it came to iPhones. The chart below shows the percentage of iPhone users that visited this site that were both supported (and controlled) by a Service Worker.</p><!--kg-card-begin: html--><div class="infogram-embed" data-id="4135e627-1332-4843-ba4b-2c2a9987b025" data-type="interactive" data-title="Copy: Mobile: Total Service Worker Usage"></div><script>!function(e,i,n,s){var t="InfogramEmbeds",d=e.getElementsByTagName("script")[0];if(window[t]&&window[t].initialized)window[t].process&&window[t].process();else if(!e.getElementById(n)){var o=e.createElement("script");o.async=1,o.id=n,o.src="https://e.infogram.com/js/dist/embed-loader-min.js",d.parentNode.insertBefore(o,d)}}(document,0,"infogram-async");</script><div style="padding:8px 0;font-family:Arial!important;font-size:13px!important;line-height:15px!important;text-align:center;border-top:1px solid #dadada;margin:0 30px"></div><!--kg-card-end: html--><p><a href="https://caniuse.com/serviceworkers">Apple&apos;s Safari browser shipped Service Worker support</a> on their browser a few years ago and it&apos;s pretty cool to see the usage increase. There has been a significant increase from 14% in 2019 to around 39% this year (so far). </p><p>In terms of the number of iPhone users to this site that are controlled by a Service Worker, this number has remained pretty stable with a slight increase this year.</p><h3 id="summary">Summary</h3><p>While I realise that my website is just a small blog and is in no way indicative of the global trend, it has been pretty cool to see how usage is changing over many years. When I first wrote about Service Worker and PWAs, it seemed like a pipe dream that Apple would even consider adding support, but as you can see from the data, these numbers are trending in the right direction.</p>]]></content:encoded></item><item><title><![CDATA[Leading Virtual Teams - Book Review]]></title><description><![CDATA[I’m always on the lookout to improve both myself and my team's daily work, so when I came across a book entitled Leading Virtual Teams by the Harvard Business Review, it really caught my eye.]]></description><link>https://deanhume.com/leading-virtual-teams-book-review/</link><guid isPermaLink="false">6419d57e92605e0b555b234d</guid><category><![CDATA[Leadership]]></category><category><![CDATA[Book Review]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Mon, 14 Mar 2022 15:08:14 GMT</pubDate><content:encoded><![CDATA[<p>When COVID first entered the world and I started working from home, my hunch was that it was only going to be for a few weeks. Two years later, that hunch has proven quite wrong! I am a people person and enjoy meeting face to face with others, so it has definitely taken me some time to adjust. </p><p>If you had asked me at the start of working remotely whether I would prefer to work at home or in the office&#x2026;..I would have chosen the office every time. As time has gone on, I have definitely started to lean towards working from home, but as you may know, that isn&#x2019;t without its challenges either. </p><p>I&#x2019;m always on the lookout to improve both myself and my team&apos;s daily work, so when I came across a book entitled <a href="https://store.hbr.org/product/leading-virtual-teams-hbr-20-minute-manager-series/10005"><em>Leading Virtual Teams</em></a> by the Harvard Business Review, it really caught my eye.<br><br></p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/b67vcydKrqlZOz3zgCngNsbQuLEnExI1DeHKo3QlT6cMuRzCHz49LBmafoQObljauI0e4wgdl65HsXOV7aeWY-kFz1QN5dPJ3U013ZCw5NLk0qBtFxxzOxu2T3UW9e33JxvSxBT8" class="kg-image" alt loading="lazy"></figure><p><br>Published in 2016, it &#xA0;was written by the authors <em>before </em>the pandemic and before so many people started working from home. What I really like about this book is that the details written in here still stand, and it hasn&#x2019;t changed the need for something like this. This book is part of the Harvard Business Review <em>20 Minute Manager Series,</em> which means it is a concise, practical primer so you&#x2019;ll have time to dive straight into the meat of the topic.</p><p>It starts off with some of the important things that you need to get right to be successful on a virtual team. First and foremost to this list is getting the right people on the team, which requires a special set of traits:</p><ul><li><strong>Communication </strong>- &#x201C;<em>Good virtual team members know how to be precise and concise in multiple media, and the err on the side of overcommunication</em>&#x201D;.</li><li><strong>Collaboration style</strong> - &#x201C;<em>Virtual teamwork requires self discipline and self motivation, since team members must stay on schedule and ask for help when necessary. Remote work is not ideal for people who need a lot of supervision</em>&#x201D;.</li><li><strong>Temperament </strong>- &#x201C;<em>Look for people who will be generous in negotiating conflict in a low information environment and resilient working alone under pressure</em>&#x201D;.</li><li><strong>Technology </strong>- &#x201C;<em>Seek out people who are open-minded to new technology and competent in tools</em>&#x201D;.</li><li><strong>Size </strong>- &#x201C;<em>When it comes to the number of folks on your team, aim low. Research shows that smaller teams are more effective and more motivated</em>&#x201D;.</li></ul><p>While you might not be in a position to build a team from scratch and choose people with these traits, it does help as a starting point to identify gaps in an existing team and tweak it accordingly.</p><p>The book also has a chapter entitled<strong> Manage the Technology</strong> in which it goes into detail about the importance of technology in the remote working world. I won&#x2019;t go too much into detail here, as each organization has their own unique tools, but the key takeaway for me was around establishing rules for the use of the technology within your organization / team. How do you share and store content? What is the etiquette for the use of this technology? While the book doesn&#x2019;t suggest which tools to use, it does talk about agreeing on using a standardised list of products. Regardless of whether you are going to use Slack or Teams - it suggests choosing <strong>one </strong>and sticking with it.</p><p>For me, one of the key areas of this book is around creating a shared vision with the team. What is the purpose of your team? Can you explain it in a clear, compelling language? Whether you are working with a team on a long-term project or a shorter goal, it&#x2019;s important to clearly define what is the vision for the team and what will make it successful. By documenting this vision and goal, you can use it as a reference point to bring everyone back to common ground when distance and time chips away at the team&apos;s cohesion.</p><p>As a People Manager, I know how tough it is to keep a remote working team engaged and happy. The book goes into some detail and provides a few tips and tricks such as:</p><ul><li>Providing praise and recognising collaborative behaviour when you see it</li><li>Encouraging people to acknowledge each other&apos;s work</li><li>Playing games together!</li><li>Finding a daily working rhythm with the team</li></ul><p>Finally, Leading Virtual Teams also touches on the common problems that you might face with remote teams. There is a part of this chapter that is dedicated to managing conflict on a virtual team. It is not a question of if, but rather when you will face conflict with a team. Dealing with this remotely can be sensitive ground and will need to be handled differently to how you might do face to face. The book provides a few examples and ideas for how to navigate this territory.</p><h2 id="final-verdict">Final Verdict</h2><p><strong>Would I buy this book?</strong> Yes, absolutely. It&#x2019;s an easy read and is filled with practical, actionable details. There isn&#x2019;t a lot of &#x201C;fluff&#x201D; - it gets to the point and leaves you with useful insights.</p><p>This book was written long before the majority of workers were forced to work remotely due to the pandemic. The authors have had real-world experience implementing this and faced challenges that they provide practical solutions to.</p><p>While some of the topics covered may come naturally to you and your organization, there are definitely some tips and tricks that you can pick up and use to improve your team&apos;s remote working. The book doesn&#x2019;t go super deep into all of these topics and perhaps skims the surface a little more than I&#x2019;d like, but after all this is entitled the 20 Minute Manager Series for a reason. I&#x2019;d still recommend you buy this book!</p>]]></content:encoded></item><item><title><![CDATA[Ghost CMS & Linux - Fixing "No Space Left on Device" Issue]]></title><description><![CDATA[I was quite surprised to found out that I was greeted with an annoying HTTP 503 error on my site over the Christmas period. After taking a closer look at the logs on Amazon, it turns out that I had actually run out of disk space on the instance.]]></description><link>https://deanhume.com/ghost-blog-fixing-no-space-left-on-device-issue/</link><guid isPermaLink="false">6419d57e92605e0b555b2348</guid><category><![CDATA[Ghost]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Thu, 07 Jan 2021 13:40:50 GMT</pubDate><content:encoded><![CDATA[<p>A few years ago, I <a href="https://deanhume.com/amazon-aws-ec2-ghost-cms-setup/">transitioned my blog from a custom ASP.NET website to Ghost CMS</a>. I&apos;ve been really happy with Ghost - it&apos;s easy to set up and get running, and the Ghost community is really great.</p><!--kg-card-begin: markdown--><p><img src="https://deanhume.com/content/images/2018/10/ghost-logo.png" alt="Ghost CMS Logo" loading="lazy"></p>
<!--kg-card-end: markdown--><p>Life often gets in the way of blogging, and I haven&apos;t made any new posts to this blog for a while. I&apos;ve had a few <a href="https://deanhume.com/amazon-aws-ec2-ghost-cms-setup/">on and off issues with Amazon EC2 Linux instances</a> and this blog over time, but generally things were working as expected. The blog has largely remained untouched for a year or so. Which is why I was quite surprised to found out that I was greeted with an annoying <strong>HTTP 503 error</strong> on my site over the Christmas period.</p><p>I thought this might just be an issue with the site, so I tried the usual <strong>Stop Instance</strong> and <strong>Restart Instance</strong>. That didn&apos;t help. </p><p>After taking a closer look at the logs on Amazon, it turns out that I had actually run out of disk space on the instance. This is a bit weird considering I had 10 GB assigned to the volume - after all, this is only a small blog!</p><h2 id="increasing-the-size-of-the-volume">Increasing the size of the volume</h2><p>My first thoughts were to get the site back up and running by increasing the size of the volume (or disk space) assigned to the instance. You can do this from the EC2 Management Console by selecting the instance, choosing <strong>Storage </strong>and the clicking on the <strong>Volume ID</strong><em> </em>(highlighted in yellow below).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://deanhume.com/content/images/2021/01/ec2-volume-id.PNG" class="kg-image" alt loading="lazy"><figcaption>Select the Volume to update</figcaption></figure><p>Next, select from the <em>Actions </em>drop down menu and choose <strong>Modify Volume</strong>.</p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2021/01/ec2-modify-volume.PNG" class="kg-image" alt loading="lazy"></figure><p>Choose the new size of the volume and select OK. </p><h2 id="using-growpart">Using Growpart</h2><p>Once you&apos;ve increased the size of the volume, it turns out that there is still one more step that needs to take place. You need to tell the partition to use the &quot;new space&quot; that you&apos;ve just given it. Without doing this, it is still assigned the old volume size and you haven&apos;t actually made use of the new space you&apos;ve given it.</p><p>You can see this by SSH&apos;ing into the instance and typing <em><strong>lsblk </strong></em>in the terminal.</p><!--kg-card-begin: markdown--><pre><code>NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   40G  0 disk
&#x2514;&#x2500;xvda1 202:1    0   10G  0 part /
loop1     7:1    0 97.9M  1 loop /snap/core/10444
loop3     7:3    0 97.9M  1 loop /snap/core/10577
loop4     7:4    0 55.4M  1 loop /snap/core18/1932
loop6     7:6    0 55.4M  1 loop /snap/core18/1944
</code></pre>
<!--kg-card-end: markdown--><p>In my case the partition <strong>xvda1 </strong>is still assigned 10 GB, but the root <strong>xvda </strong>uses 40 GB. </p><p>The simple solution is to run the following command on the root partition and tell it to start using the new space it has been allocated:</p><!--kg-card-begin: markdown--><p><code>$ sudo growpart /dev/xvda 1 </code></p>
<!--kg-card-end: markdown--><p>However, when I did this I was presented with the following error:</p><pre><code>mkdir: cannot create directory &#x2018;/tmp/growpart.2626&#x2019;: No space left on device</code></pre><!--kg-card-begin: markdown--><p>Arrrgh! This meant that I didn&apos;t even have enough disk space to expand the disk space. I was screwed! One of the suggestions that was mentioned online was to use the <code>$ apt-get autoremove command</code> to remove those dependencies that were installed with applications and are no longer used by anything else on the system. Unfortunately, I was also met with the &quot;<em>No space left on device</em>&quot; error when I ran the command.</p>
<!--kg-card-end: markdown--><p>I searched for other files to remove, but being a bit of an amateur with Linux, I decided to delete the safest files....the log files. I did this by typing the following command in the terminal:</p><!--kg-card-begin: markdown--><pre><code>$ find /var/log -type f -delete
</code></pre>
<!--kg-card-end: markdown--><p>Whew! This bought me an additional 300 Mb which was just enough to run the growpart command again. </p><!--kg-card-begin: markdown--><p><code>$ sudo growpart /dev/xvda 1</code></p>
<!--kg-card-end: markdown--><p>Success! If I run the <strong>lsblk</strong> command to verify that partition 1 is expanded to 40 GB, I see the following:</p><!--kg-card-begin: markdown--><pre><code>NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   40G  0 disk
&#x2514;&#x2500;xvda1 202:1    0   40G  0 part /
loop1     7:1    0 97.9M  1 loop /snap/core/10444
loop3     7:3    0 97.9M  1 loop /snap/core/10577
loop4     7:4    0 55.4M  1 loop /snap/core18/1932
loop6     7:6    0 55.4M  1 loop /snap/core18/1944
</code></pre>
<!--kg-card-end: markdown--><p>I ran the resize2fs command to resize my file system. It can be used to enlarge or shrink an unmounted file system located on device.</p><!--kg-card-begin: markdown--><p><code>$ sudo resize2fs /dev/xvda1</code></p>
<!--kg-card-end: markdown--><p>Finally, I started the Ghost CMS again with the following <a href="https://deanhume.com/amazon-aws-ec2-ghost-cms-setup/#stoppingandstartingghostagain">command</a>:</p><!--kg-card-begin: markdown--><p><code>sudo /opt/bitnami/ctlscript.sh start</code></p>
<!--kg-card-end: markdown--><p>Once I ran that command I was greeted with:</p><!--kg-card-begin: markdown--><pre><code class="language-&#x2139;">&#x2139; Checking if logged in user is directory owner [skipped]
&#x2714; Checking current folder permissions
&#x2714; Validating config
&#x2714; Checking memory availability
&#x2714; Starting Ghost
You can access your publication at http://xx.xxx.xx.x:80
Your admin interface is located at http://xx.xxx.xx.x:80/ghost/
</code></pre>
<!--kg-card-end: markdown--><p>I never thought I&apos;d be so happy to see the output from a terminal!</p><h2 id="summary">Summary</h2><p>I still haven&apos;t been able to get to the bottom of why a small Ghost CMS should be taking up such a large amount of disk space, but for the moment I am happy that my site is up and running. I have Cloudwatch alarms in place to alert me if the disk space grows too large now.</p><p>If anyone has any guesses as to why this happens with Ghost hosted on Linux, let me know!</p>]]></content:encoded></item><item><title><![CDATA[Book Review - Accelerate: The Science of Lean Software and Devops]]></title><description><![CDATA[During my morning coffee session, I came across the book - Accelerate: The Science of Lean Software and Devops, and the title of the book instantly drew me in.]]></description><link>https://deanhume.com/accelerate-devops-book-review/</link><guid isPermaLink="false">6419d57e92605e0b555b2347</guid><category><![CDATA[Book Review]]></category><category><![CDATA[Book]]></category><category><![CDATA[Leadership]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Mon, 03 Aug 2020 15:51:44 GMT</pubDate><content:encoded><![CDATA[<p>During my morning coffee session, I like to check through my RSS feed and see what is happening out there in the world of technology. I came across this article by the Spotify R&amp;D team entitled &#x201C;<a href="https://engineering.atspotify.com/2020/07/22/leveraging-mobile-infrastructure-with-data-driven-decisions/">Leveraging Mobile Infrastructure with Data-Driven Decisions</a>&#x201D;. They referenced the book <a href="https://itrevolution.com/book/accelerate/"><strong>Accelerate: The Science of Lean Software and Devops</strong></a>, and the title of the book instantly drew me in.</p><p>I&#x2019;ve just finished reading the book and I can say that I definitely enjoyed it.</p><figure class="kg-card kg-image-card"><img src="https://deanhume.com/content/images/2020/08/accelerate.jpeg" class="kg-image" alt loading="lazy"></figure><p>Through four years of research, the authors set out to find a way to measure software delivery performance&#x2015;and what drives it&#x2015;using rigorous statistical methods. This book presents both the findings and the science behind that research, making the information accessible for readers to apply in their own organizations. Personally, I think it&apos;s quite cool to see scientific data applied to some of the everyday things that we do in technology organizations. I also found it interesting to see how some of the top performing companies repeatedly seemed to pull away from the &#x201C;pack&#x201D; by simply applying many of the techniques applied in this book. The book takes a look at well-known best practices such as Continuous Delivery, Lean Management, and Transformational Leadership.</p><p>If you are looking for a book that tells you <em>how </em>to build and scale a high performing technology organisation, then this book doesn&#x2019;t quite cover it. This book goes more into <strong><em>why </em></strong>you should be doing things a certain way and backs this up with facts.</p><p>Part 1 of this book explores what the research team found after trawling through the data. Part 2 dives into the science behind the book and an introduction to Psychometrics. Finally Part 3, looks into transformation, which focuses on how leadership and management can help drive these improvements.</p><p>If I had one takeaway from this book (which surprised me), it was that the leadership of an organisation plays a pivotal role in the team&apos;s results. I really liked this quote:</p><blockquote>&#x201C;Leadership really does have a powerful impact on results. A good leader affects a team&#x2019;s ability to deliver code, architect good systems, and apply Lean principles to how the team manages its work and develops products. All of these have a measurable impact on satisfaction, efficiency, and the ability to achieve organisational goals&#x201D;.</blockquote><p>Overall, this is a great book and definitely gives a good insight into why some of the best practices that we take for granted are worth doing and lead to high performing technology organisations. This book concentrates more on the why as opposed to the how. If you are looking for a how to, I&#x2019;d recommend reading <a href="https://www.amazon.co.uk/DevOps-Handbook-World-Class-Reliability-Organizations-ebook/dp/B09G2GS39R">The DevOps Handbook</a> to learn more.</p><p>I hope you enjoy the book!</p>]]></content:encoded></item><item><title><![CDATA[The People Manager: A Guide for the First Time Manager - has been published!]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Behind the scenes, I have been working on a new book with my EA colleague <a href="https://www.linkedin.com/in/jo-root-17b6371/">Jo Root</a>. I am super excited to announce that <a href="https://www.amazon.co.uk/People-Manager-Guide-First-Managers-ebook/dp/B084WVGG94">The People Manager: A Guide for the First Time Manager</a> has been published! This is a personal project that has been a year in the making.</p>]]></description><link>https://deanhume.com/book-the-people-manager/</link><guid isPermaLink="false">6419d57e92605e0b555b2346</guid><category><![CDATA[Book Review]]></category><category><![CDATA[Book]]></category><category><![CDATA[Leadership]]></category><dc:creator><![CDATA[Dean Hume]]></dc:creator><pubDate>Thu, 26 Mar 2020 10:45:04 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Behind the scenes, I have been working on a new book with my EA colleague <a href="https://www.linkedin.com/in/jo-root-17b6371/">Jo Root</a>. I am super excited to announce that <a href="https://www.amazon.co.uk/People-Manager-Guide-First-Managers-ebook/dp/B084WVGG94">The People Manager: A Guide for the First Time Manager</a> has been published! This is a personal project that has been a year in the making.</p>
<p><img src="https://deanhume.com/content/images/2020/03/the-people-manager-book.jpg" alt="The People Manager - A Guide for the First Time Manager - Book" loading="lazy"></p>
<p>The book is designed to de-mystify the challenges of People Management and give best practices, personal hints and tips from our own experience, and splitting up what might seem an overwhelming new role into clear, focused and hopefully manageable chapters of advice and coaching.</p>
<p><img src="https://deanhume.com/content/images/2020/03/people-manager-book.jpg" alt="People Manager Book - A Guide for the First Time Manager" loading="lazy"></p>
<p>Becoming a People Manager is an exciting new challenge in your career but can also feel overwhelming. Being new to the role will no doubt mean that you have many questions that you want to find answers for. Your previous work experience, whilst invaluable for your personal development, may not give you the tools or confidence you need to springboard straight into your new role.</p>
<p>This book takes you through practical steps and real-world examples to become a confident, and ultimately successful People Manager.</p>
<p>Our hope is that you will:</p>
<ul>
<li>Discover tried-and-tested examples of tools and process you can use in your new role.</li>
<li>Explore the role of a People Manager and learn about what to expect.</li>
<li>Learn how to run effective and engaging one-on-one sessions with your team.</li>
<li>Master the art of building happy teams.</li>
<li>Learn how to handle conflict amongst your teams.</li>
<li>Discover how to continually learn and grow in your new role.</li>
</ul>
<p>We hope that new People Managers out there (or those returning to the role) will find this book invaluable for your learning, growth and success in your career.</p>
<p>The book is now live on <a href="https://www.amazon.co.uk/People-Manager-Guide-First-Managers-ebook/dp/B084WVGG94">Amazon</a> and available to order!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>