<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>JMPInline</title>
	<atom:link href="https://blog.nerdbank.net/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.nerdbank.net</link>
	<description>Read up on .NET news, tips, cautions... and other areas of technological interest.</description>
	<lastBuildDate>Sat, 01 Mar 2025 17:12:49 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.7.2</generator>
	<item>
		<title>The case for strong naming .NET assemblies</title>
		<link>https://blog.nerdbank.net/2025/03/the-case-for-strong-naming-net-assemblies</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Sat, 01 Mar 2025 17:09:48 +0000</pubDate>
				<category><![CDATA[Software engineering]]></category>
		<category><![CDATA[.NET]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=612</guid>

					<description><![CDATA[I&#8217;ve observed many .NET open-source projects whose authors either don&#8217;t know about strong name signing or are actively opposed to it. In this post, I&#8217;ll explain what strong name signing is, when folks should use it, and refute several common points of misinformation out there that tends to dissuade people from strong name signing. What [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;ve observed many .NET open-source projects whose authors either don&#8217;t know about strong name signing or are actively opposed to it. In this post, I&#8217;ll explain what strong name signing is, when folks should use it, and refute several common points of misinformation out there that tends to dissuade people from strong name signing.</p>



<h2 class="wp-block-heading">What is strong naming</h2>



<p>Microsoft describes strong naming in their doc <a href="https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/strong-naming">Strong naming and .NET libraries &#8211; .NE</a><a href="https://learn.microsoft.com/dotnet/standard/library-guidance/strong-naming">T.</a> In short, it changes an assembly name from a simple &#8220;MyUtilities, Version=1.0.0.0, Culture=neutral&#8221; to a longer &#8220;MyUtilities, Version=1.0.0.0, Culture=neutral, <strong>PublicKeyToken=a4e8f1e6cbfd650b</strong>&#8220;. </p>



<p>The added PublicKeyToken is a hash for a much longer PublicKey, which has a companion private key that is used to sign the assembly. A strong named assembly is <em>typically</em> signed with this private key, but not always. More on that later.</p>



<p>Strong naming is <em>free</em>. While authenticode signing requires purchasing a code signing certificate, strong name signing does <em>not</em>. These two signing methods address different needs and should not be looked at as alternatives, since they may be used together.</p>



<h2 class="wp-block-heading">Why strong name?</h2>



<p>TLDR: <em>Everyone</em> can reference a strong named assembly. But a non-strong named assembly can only be referenced by other non-strong named assemblies.</p>



<p>Microsoft list several reasons for <a href="https://learn.microsoft.com/en-us/dotnet/standard/assembly/strong-named#why-strong-name-your-assemblies">Why strong-name your assemblies?</a> that I suggest you review.</p>



<p>In <a href="https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/strong-naming">their official guidance</a>, the .NET team espouses strong naming for avoiding assembly naming conflicts:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Strong naming refers to signing an assembly with a key, producing a <a href="https://learn.microsoft.com/en-us/dotnet/standard/assembly/strong-named">strong-named assembly</a>. When an assembly is strong-named, it creates a unique identity based on the name and assembly version number, and it can help prevent assembly conflicts.</p>
</blockquote>



<p>This means if two people produce assemblies called Utilities.dll, .NET Framework can load both of them without getting confused. It still puts an onus on the app developer to deploy these two assemblies to different directories so they don&#8217;t clash in the file system, and have an .exe.config file that points to each of them, but it&#8217;s only <em>possible </em>if they are strong named. You should still name your assemblies uniquely to avoid conflicts when possible.</p>



<p>Strong naming isn&#8217;t about <em>security</em>. The .NET team has said as much officially. They further state:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><img src="https://s.w.org/images/core/emoji/15.0.3/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> CONSIDER adding the strong naming key to your source control system.<br>A publicly available key lets developers modify and recompile your library source code with the same key.</p>
</blockquote>



<p>And&#8230;</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>When the identity of the publisher of the code is desired,&nbsp;<a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/install/authenticode">Authenticode</a>&nbsp;and&nbsp;<a href="https://learn.microsoft.com/en-us/nuget/create-packages/sign-a-package">NuGet Package Signing</a>&nbsp;are recommended.</p>
</blockquote>



<p>And in <a href="https://learn.microsoft.com/dotnet/framework/tools/sn-exe-strong-name-tool">their sn tool documentation</a>&#8230;</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Do not rely on strong names for security. They provide a unique identity only.</p>
</blockquote>



<p>So you see, strong naming is&nbsp;<em>not</em>&nbsp;for verifying the authenticity of the publisher. Therefore, disclosing the .snk publicly in an OSS repo is perfectly fine. You lose&nbsp;<em>nothing</em>&nbsp;as an OSS non-strong-named library by adding a strong name (except binary compatibility with previous versions of your library when a user is running on .NET Framework). You didn&#8217;t have publisher security before, and you don&#8217;t after. But you&nbsp;<em>gain</em>&nbsp;a whole set of .NET Framework customers who already strong name their assemblies and are technically prevented from referencing assemblies that are not strong named.</p>



<p>Many OSS repos (including&nbsp;<em>all</em>&nbsp;of my own) check in the .snk for their own use and to maintain the spirit of OSS, which is that anyone can modify and rebuild an assembly and use it in place of the original. Strong naming and&nbsp;<em>hiding</em>&nbsp;the private key would violate that, but Strong naming and disclosing the private key maintains that.</p>



<h2 class="wp-block-heading">Strong Name signing verification</h2>



<p>.NET Framework usually doesn&#8217;t verify the strong name signature at all, and .NET <em>never</em> does. This further confirms that strong naming isn&#8217;t about security. .NET Framework only verifies a strong name signature when an assembly is ngen&#8217;d, installed in the GAC, or shadow copied (and a few other less common scenarios). None of these happen in the normal process of loading and executing an assembly in an application.</p>



<p>Because these runtimes so rarely (or never) verify the signature, the C# compiler added a switch to do what it calls <a href="https://github.com/dotnet/runtime/blob/main/docs/project/public-signing.md">public signing</a>. With public signing, a <em>public </em>key is used to strong name an assembly, but its signature is all zeros, and the private key is never used. This is an invalid signature, but it works fine on .NET. It also works on .NET Framework so long as the assembly is not GAC&#8217;d, ngen&#8217;d, or shadow copied, etc. If you release assemblies for others to use, please use properly signed as</p>



<p>For completeness, I&#8217;ll mention that another concept called <a href="https://learn.microsoft.com/dotnet/standard/assembly/delay-sign">delay signing</a> also exists. There are precious few reasons to do this and it significantly complicates a build. I do not advocate anyone to start doing this.</p>



<h2 class="wp-block-heading">Caveats</h2>



<p>A strong named assembly has a couple limitations:</p>



<ol class="wp-block-list">
<li>It can only reference other strong named assemblies.</li>



<li>It can only expose its internals via InternalsVisibleTo to other strong named assemblies.</li>
</ol>



<p>These constraints put something of a semi-viral requirement on a family of assemblies, whether they are strong named or not. If a team or product tends to strong name their assemblies, their dependencies need to as well. Similarly, if that team does <em>not</em> strong name their assemblies, no one downstream of them can either. </p>



<p>If it&#8217;s viral either way, how do you decide which side to be on? Well, as an individual assembly author, you gain more audience by being strong named. You only <em>win</em> users by strong naming, because <em>everyone</em> can reference a strong named assembly, while only a subset of users can reference your library if you <em>don&#8217;t</em> strong name.</p>



<h2 class="wp-block-heading">Counter-arguments and rebuttals</h2>



<p><strong>Claim:</strong> &#8220;Only enterprises strong name their assemblies.&#8221;</p>



<p>Fact check: false. For reasons given above, many developers including OSS developers strong name their assemblies to increase their audience. </p>



<p><strong>Claim:</strong> &#8220;If someone wants to use my library, they can strong name it themselves, or pay me to strong name a private copy for them.&#8221;</p>



<p>There are a couple tools out there that automate adding a strong name to an existing assembly. There are significant downsides to this, however. Applying a strong name to an existing assembly changes its identity for the CLR, so any other assembly that referenced the original will refuse to run against the strong named version.</p>



<p><strong>Claim:</strong> &#8220;I can ship a strong named and non-strong named assembly in separate nuget packages.&#8221;</p>



<p>This is about as disastrous as can be. Consider you ship package Z and Z.Signed. Along comes nuget package M which is strong named so it depends on Z.Signed. Package N is <em>not</em> strong named and decides to depend on Z. Now app or package A comes along and consumes M and N. Now we have a problem, because Z.dll is in the dependency graph twice &#8212; once with a strong name and once without. The compiler of A.exe or A.dll gets all the types in Z.dll twice and you get compile errors. You also have deployment issues because msbuild tries to copy Z.dll twice. And .NET won&#8217;t be able to load both anyways.</p>



<p>Please do <em>not</em> ship both strong named and non-strong named assemblies. Just add a strong name to your existing assembly and bump the major version number (because it&#8217;s a binary breaking change for .NET Framework users).</p>



<h2 class="wp-block-heading">Strong naming and .NET </h2>



<p>While .NET Framework considers a strong name to be a crucial part of an assembly&#8217;s name, .NET (as in Core CLR) does <em>not</em>. By default, .NET disregards a strong name completely. This has a couple ramifications:</p>



<ol class="wp-block-list">
<li>Strong naming is <em>not</em> a means to load multiple assemblies with the same name but from different publishers into the same process for a .NET app.</li>



<li>As .NET Framework gives way to .NET, the call to strong name assemblies becomes less relevant. However, there are still <em>many</em> folks who run on or at least support .NET Framework, which is still fully supported by Windows. So this is a long way off.</li>
</ol>



<h2 class="wp-block-heading">How to start strong naming your assembly</h2>



<p>Giving your assembly a strong name is easy and free. First, generate a strong name signing key with <a href="https://learn.microsoft.com/en-us/dotnet/framework/tools/sn-exe-strong-name-tool">the <code>sn</code> tool</a>:</p>



<pre class="wp-block-code"><code><code>sn -k strongname.snk</code></code></pre>



<p>The <code>sn</code> tool comes with the .NET Framework SDK, which you can freely install if you have a Windows machine handy. Or on a non-Windows machine you can install <a href="https://www.mono-project.com/">mono</a> to get <code>sn</code> as well. The .snk file that you generate will work on any OS and to build any assembly with the .NET SDK via the <code>dotnet build</code>.</p>



<p>Go ahead and check in the .snk file you generate into your source control system. Yes, it contains a private key, but as stated above, strong naming isn&#8217;t about security so sharing this private key publicly is just fine. I do this for <em>all</em> my OSS repos.</p>



<p>Now add two properties to your project.</p>



<pre class="wp-block-code"><code>&lt;PropertyGroup>
  &lt;SignAssembly>true&lt;/SignAssembly>
  &lt;AssemblyOriginatorKeyFile>strongname.snk&lt;/AssemblyOriginatorKeyFile>
&lt;/PropertyGroup></code></pre>



<p>Alternatively, you can define these properties in your Directory.Build.props file at the root of your repo to strong name sign all your projects at once. Move the strongname.snk file to the root of your repo and then add these properties:</p>



<pre class="wp-block-code"><code>&lt;PropertyGroup>
  &lt;SignAssembly>true&lt;/SignAssembly>
  &lt;AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)strongname.snk&lt;/AssemblyOriginatorKeyFile>
&lt;/PropertyGroup></code></pre>



<p>At this point, you <em>may</em> be done. Try building. The build may fail for a couple reasons:</p>



<ol class="wp-block-list">
<li>Your projects reference assemblies that are not yet strong named. If these are 3rd party dependencies, file bugs against them asking them politely to strong name their assemblies. Link to this article to help alleviate their concerns and instruct them on how to proceed. Your progress in strong naming your assembly is blocked until your dependencies strong name or you drop those dependencies.</li>



<li>You have <code>InternalsVisibleTo</code> attributes in your project that need to be updated to include the PublicKey of the referencing assemblies (which now also must be strong named). Since you presumably have control over the assemblies that you are offering internals visibility to, you should be able to strong name these as well and then update your attribute. Getting the value to use in PublicKey can be tough at the command line. But in a C#-aware IDE like Visual Studio, a completion list is offered that makes updating these attributes really easy. And if you&#8217;re using <a href="https://github.com/dotnet/Nerdbank.GitVersioning/">Nerdbank.GitVersioning</a> you can just reference <code>ThisAssembly.PublicKey</code> to get the value:<br><code>[InternalsVisibleTo("My.Tests, PublicKey=" + ThisAssembly.PublicKey)]</code><br>Note that this is the public key itself rather than the much shorter public key <em>token</em> seen in an assembly&#8217;s strong name.</li>
</ol>



<p>Congratulations on strong naming your assembly. And thank you for making your assemblies available to a broader audience.</p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F03%2Fthe-case-for-strong-naming-net-assemblies&amp;linkname=The%20case%20for%20strong%20naming%20.NET%20assemblies" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F03%2Fthe-case-for-strong-naming-net-assemblies&amp;linkname=The%20case%20for%20strong%20naming%20.NET%20assemblies" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F03%2Fthe-case-for-strong-naming-net-assemblies&amp;linkname=The%20case%20for%20strong%20naming%20.NET%20assemblies" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F03%2Fthe-case-for-strong-naming-net-assemblies&amp;linkname=The%20case%20for%20strong%20naming%20.NET%20assemblies" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F03%2Fthe-case-for-strong-naming-net-assemblies&amp;linkname=The%20case%20for%20strong%20naming%20.NET%20assemblies" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F03%2Fthe-case-for-strong-naming-net-assemblies&#038;title=The%20case%20for%20strong%20naming%20.NET%20assemblies" data-a2a-url="https://blog.nerdbank.net/2025/03/the-case-for-strong-naming-net-assemblies" data-a2a-title="The case for strong naming .NET assemblies"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Can We Trust LLMs to Write Secure Code? The Hidden Risks Developers Need to Know</title>
		<link>https://blog.nerdbank.net/2025/01/should-software-developers-trust-deepseek</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Wed, 29 Jan 2025 16:00:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Software engineering]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=595</guid>

					<description><![CDATA[DeepSeek is being hailed as the latest breakthrough in large language models (LLMs), with many calling it "open source." But is it truly open source, and can we trust it? This article dives into the nuances of what "open source" means for LLMs, arguing that publicly available weights don’t equate to true transparency. Exploring risks like code injection and indirect poisoning, it questions whether we can ever fully trust LLMs—especially those from untrusted sources—to handle critical tasks like writing code or making decisions that impact our lives and security. What does the future hold for verifying the trustworthiness of these powerful models?]]></description>
										<content:encoded><![CDATA[
<p><a href="https://chat.deepseek.com/">DeepSeek</a> is the newest and hottest large language model (LLM) on the block. Many claim <a href="https://github.com/deepseek-ai/DeepSeek-V3">it is open source</a>. But what does that really mean, and can we trust it? I argue: no.</p>



<p>We call it open source because its parameter weights are publicly available, making it self-hostable. But what are these weights? Simply put, they’re a bunch of numbers. We have no idea what they mean except through experimentation—sending in prompts and observing the outputs. This is less transparent than an executable binary because, at least with a binary, you can inspect the text strings inside. Instead of calling an LLM with published weights &#8220;open source,&#8221; we should think of it as a freely redistributable binary that is both encrypted and executable. Would you run that on your machine? In fact, Ben Thompson has also <a href="https://stratechery.com/2025/deepseek-faq/">highlighted this issue</a> and proposed the term &#8220;open weights&#8221; for LLMs like DeepSeek.</p>



<p>So, what would truly constitute &#8220;open source&#8221; for an LLM? Like traditional open source, it would need to be reproducible. A binary is considered open source if you can recompile it from human-readable source code and produce the same binary, byte for byte. Otherwise, you can’t be sure what went into that binary. Recompiling from source is relatively inexpensive, but recreating an LLM’s weights from its source—including all the inputs, training instructions, and more—would be prohibitively expensive in terms of computational and hardware costs. Most people wouldn’t (and couldn’t) do this just to satisfy their curiosity about verification.</p>



<p>If the organizations behind an LLM like DeepSeek share their weights under a freely redistributable license but don’t provide the exact instructions and inputs needed to recreate those weights (or if no trusted organization has verified the process), we must treat it like an encrypted executable. But why? What harm could an LLM possibly cause?</p>



<p>The potential harms are significant. There’s the human safety factor, censorship concerns, and more—issues that organizations like OpenAI regularly test for before releasing their models. But when we consider LLMs published by countries or organizations we don’t trust, there’s an even more nefarious possibility: <strong>code injection</strong>.</p>



<p>Yes, one of the most popular and growing uses of LLMs is to write source code for developers. Developers might prompt an LLM to write a program to accomplish a specific task, and the LLM can often do a surprisingly good job. But what if that LLM were trained to write programs with a hidden second purpose? What if it generated code with backdoors that only a meticulous audit could detect? After all, security vulnerabilities in software are already hard to find, and those are usually accidental. What if security holes were deliberately introduced, allowing the LLM’s creator to exploit programs running on computers, devices, and web apps?</p>



<p>There’s no way to vet a black-box LLM—even a freely redistributed one like DeepSeek—against this risk. You could reduce the risk by asking it to generate many programs and then carefully reviewing the code for signs of malicious training, but that’s like searching a 1-billion-pixel photo for a specific shade of blue, one pixel at a time. And it’s possible that only certain prompts, like &#8220;create a login page for a government website,&#8221; would trigger the generation of malicious backdoor code, making it unlikely to be detected during testing.</p>



<p>Another possibility is <strong>indirect poisoning</strong> of an LLM. Imagine commissioning a group of people to create hundreds of simple applications and publish their source code on GitHub. These apps could contain hidden backdoors, but their code comments and documentation would tout their high quality and functionality. Eventually, an LLM developer might use this open-source code to train their model, inadvertently incorporating the faulty, backdoored code. In this scenario, even &#8220;trusted&#8221; LLMs could end up producing exploitable code.</p>



<p>Now, I’m a huge fan of DeepSeek R1 (the reasoning version). I’m absolutely amazed at what it can do, and I appreciate that it’s freely available online. But it makes me wonder: who will we trust to create the LLMs that write our code, make health decisions, and handle other critical tasks that we eventually rely on for our safety and security? Will we ever develop a better way to directly vet the trustworthiness of LLMs, rather than relying solely on the reputation of their creators?</p>



<p>What do you think?</p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F01%2Fshould-software-developers-trust-deepseek&amp;linkname=Can%20We%20Trust%20LLMs%20to%20Write%20Secure%20Code%3F%20The%20Hidden%20Risks%20Developers%20Need%20to%20Know" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F01%2Fshould-software-developers-trust-deepseek&amp;linkname=Can%20We%20Trust%20LLMs%20to%20Write%20Secure%20Code%3F%20The%20Hidden%20Risks%20Developers%20Need%20to%20Know" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F01%2Fshould-software-developers-trust-deepseek&amp;linkname=Can%20We%20Trust%20LLMs%20to%20Write%20Secure%20Code%3F%20The%20Hidden%20Risks%20Developers%20Need%20to%20Know" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F01%2Fshould-software-developers-trust-deepseek&amp;linkname=Can%20We%20Trust%20LLMs%20to%20Write%20Secure%20Code%3F%20The%20Hidden%20Risks%20Developers%20Need%20to%20Know" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F01%2Fshould-software-developers-trust-deepseek&amp;linkname=Can%20We%20Trust%20LLMs%20to%20Write%20Secure%20Code%3F%20The%20Hidden%20Risks%20Developers%20Need%20to%20Know" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2025%2F01%2Fshould-software-developers-trust-deepseek&#038;title=Can%20We%20Trust%20LLMs%20to%20Write%20Secure%20Code%3F%20The%20Hidden%20Risks%20Developers%20Need%20to%20Know" data-a2a-url="https://blog.nerdbank.net/2025/01/should-software-developers-trust-deepseek" data-a2a-title="Can We Trust LLMs to Write Secure Code? The Hidden Risks Developers Need to Know"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>A fresh MessagePack library</title>
		<link>https://blog.nerdbank.net/2024/11/a-fresh-messagepack-library</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Tue, 12 Nov 2024 21:42:45 +0000</pubDate>
				<category><![CDATA[Software engineering]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[MessagePack]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=583</guid>

					<description><![CDATA[I&#8217;ve been honored to work on the MessagePack-CSharp library since at least 2019. I&#8217;ve learned a lot from its founder in the process. We shared a lot of values, and his library is hugely successful, with over 150 million downloads from nuget.org. But the library has a lot of history in it now, and runtimes [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;ve been honored to work on the <a href="https://github.com/MessagePack-CSharp/MessagePack-CSharp/">MessagePack-CSharp</a> library since at least 2019. I&#8217;ve learned a lot from its founder in the process. We shared a lot of values, and his library is hugely successful, with over 150 million downloads from nuget.org. But the library has a lot of history in it now, and runtimes have evolved dramatically. The library is far more complex than it needs to be and it&#8217;s making new features and maintenance far too expensive for my limited time. It&#8217;s upcoming v3 release will be the last major release that I expect to contribute to. As I&#8217;ve been the primary contributor to the library for the last few years, that <em>may </em>mean that MessagePack-CSharp won&#8217;t evolve much going forward. But that will be up to our co-owner to see if he wants to keep investing in it.</p>



<p>The <a href="https://msgpack.org/">MessagePack</a> format, by the way, is a compact binary format that is available for every platform and virtually every language. It is similar to JSON but has more features.</p>



<p>For my part, I&#8217;m shifting my attention to my <a href="https://aarnott.github.io/Nerdbank.MessagePack/docs/features.html">new Nerdbank.MessagePack library</a>. It&#8217;s just about as fast as MessagePack-CSharp, <em>much</em> simpler to use, and so much simpler to maintain that I could achieve rough feature parity with MessagePack-CSharp in just a couple weeks. It also has unique features including async serialization support, .NET trimming and NativeAOT support. It is designed to allow faster innovation and greater flexibility in use cases. It has a whole bunch of C# analyzers to help you do the right thing, great docs, and includes migration helps to automate most of the work should you choose to switch your application to use it.</p>



<p>Learn more about <a href="https://github.com/AArnott/Nerdbank.MessagePack?tab=readme-ov-file#performance">perf comparisons and why the need for a new library</a> over at the github repo.</p>



<p>I have to give a shout out to Eirik Tsarpalis for his most excellent and exciting <a href="https://eiriktsarpalis.github.io/PolyType/">PolyType</a> library, which is what inspired this new library and made it so easy to build while supporting modern NativeAOT.</p>



<p>So please, check out Nerdbank.MessagePack for your serialization needs.</p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F11%2Fa-fresh-messagepack-library&amp;linkname=A%20fresh%20MessagePack%20library" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F11%2Fa-fresh-messagepack-library&amp;linkname=A%20fresh%20MessagePack%20library" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F11%2Fa-fresh-messagepack-library&amp;linkname=A%20fresh%20MessagePack%20library" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F11%2Fa-fresh-messagepack-library&amp;linkname=A%20fresh%20MessagePack%20library" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F11%2Fa-fresh-messagepack-library&amp;linkname=A%20fresh%20MessagePack%20library" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F11%2Fa-fresh-messagepack-library&#038;title=A%20fresh%20MessagePack%20library" data-a2a-url="https://blog.nerdbank.net/2024/11/a-fresh-messagepack-library" data-a2a-title="A fresh MessagePack library"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>From qwerty to Dvorak to qwerty</title>
		<link>https://blog.nerdbank.net/2024/08/from-qwerty-to-dvorak-to-qwerty</link>
					<comments>https://blog.nerdbank.net/2024/08/from-qwerty-to-dvorak-to-qwerty#comments</comments>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Fri, 30 Aug 2024 16:30:00 +0000</pubDate>
				<category><![CDATA[Life]]></category>
		<category><![CDATA[Reviews]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=575</guid>

					<description><![CDATA[I was sold a lie. Dvorak isn&#8217;t superior. Sure, it relies on the same reasoning that keeps the U.S. on the old British measuring system instead of switching to the International System of Units (aka &#8216;metric&#8217;), but that&#8217;s a dang good reason. Even if it is unsatisfying. I was a fast qwerty typist. 100 WPM [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I was sold a lie. Dvorak isn&#8217;t superior. Sure, it relies on the same reasoning that keeps the U.S. on the old British measuring system instead of switching to the International System of Units (aka &#8216;metric&#8217;), but that&#8217;s a dang good reason. Even if it is unsatisfying.</p>



<p>I was a fast qwerty typist. 100 WPM was typical for me. But some were faster and I was interested both in getting faster and in improving the ergonomics to avoid things like carpal tunnel. Dvorak is sold as technically superior. It was scientifically engineered for speed and ergonomics, whereas <a href="https://www.edclub.com/sportal/program-3/175.play">qwerty was allegedly arranged deliberately to slow typists down to avoid old mechanical type writers from jamming up</a>. Sure, it would mean I need to learn a new keyboard layout. But I&#8217;m a fast learner. How hard could it be?</p>



<p>Add to that the attraction of the <a href="https://kinesis-ergo.com/shop/advantage2/">Kinesis Advantage2 keyboard</a>, with both unique and compelling ergonomics and sold with optional key caps for Dvorak. And a friend and coworker who swore by Dvorak and this keyboard. I decided to go all in. </p>



<h2 class="wp-block-heading">Switching to and living with Dvorak</h2>



<p>Rather than taking a month or two to transition as my friend suggested, it took <em>many</em> months before I could consider myself fluent in Dvorak. Knowing qwerty made it difficult to learn Dvorak until I immersed myself in it &#8212; not allowing myself to ever use Qwerty. I used <a href="https://www.edclub.com/sportal/">EdClub</a> to learn Dvorak as they have lessons specifically for that layout, which are easy and fun.</p>



<p>Dvorak is great. It <em>is</em> more ergonomic. The claim that the most commonly typed characters are on the home row makes a huge difference. Sometimes it feels like the whole alphabet is on the home row because I can type so much without moving my fingers far at all. And the Advantage2 keyboard magnifies this amazing experience.</p>



<p>My Dvorak typing speed is 117 WPM. At best, marginally faster than my speed with Qwerty.</p>



<p>Using Dvorak has several very real, practical problems:</p>



<p>When you learn a second human language, you may be able to speak it and your original language fluently. But with typing, at least in my experience your fingers only have muscle memory enough for one keyboard layout. My qwerty typing is almost non-existent at this point, which leads into the first real problem&#8230;</p>



<p>Every single keyboard around me is qwerty except <em>one </em>of mine. When my family want to use my computer, I have to plug in an alternate keyboard so they can actually type. Every computer in my house has dual keyboard layouts configured in the OS so that I can switch to Dvorak when using them and my family can use Qwerty. All my family members then have to learn how to switch layouts in the OS in case I used it last. When I&#8217;m using a friend&#8217;s computer, I have to type with their qwerty layout and it&#8217;s painfully slow, and to save embarrassment I have to explain that I <em>am</em> a good typist &#8212; on Dvorak.</p>



<p>Keyboard commands in games and keyboard shortcuts in apps are all based on character placement on a qwerty keyboard. Cut/Copy/Paste shortcuts are all neatly in a row as Ctrl+C, X and V &#8212; on <em>qwerty</em>. They can also be (and this is <em>super</em> useful) all executed with just the left hand, while my right hand is operating the mouse, busy selecting something to copy and pointing where to paste. But on Dvorak, two of these characters are on the <em>right</em> side of the keyboard, leaving only X (Cut) operable with the left hand. There are many other keystrokes that I had learned to do with one hand, or perhaps with two hands even with 3-4 keys at once, because of their qwerty placement, that on Dvorak are much less convenient. </p>



<p>I&#8217;m not a big gamer, but on the occasional time I play, games that use the keyboard (A-S-D-W anyone?), I have to fumble through game settings and redefine all the buttons so they are convenient on Dvorak. And unlike the OS which has convenient ways to swap between Dvorak and Qwerty, games I&#8217;ve played tend to only have one keyboard mapping, making it a major pain to switch between my own layout and one that works for literally everyone else I share a computer with.</p>



<p>Now, all my computers that I directly interface with are laptops. So this Advantage2 keyboard I use is only useful with my machine when my laptop is docked on my primary work desk. That means my laptop has to itself have the software keyboard layout switch ready to go too, since the Advantage2 masquerades as Qwerty to the OS, but the built-in keyboard doesn&#8217;t. If that&#8217;s confusing to you, that&#8217;s because it is. But compound that with the fact that most of my work isn&#8217;t on my laptop OS at all&#8230; I usually remote into another machine to get work done. So now all the <em>remote </em>machines I connect to also need 2 keyboard layouts configured. And the keyboard shortcut to switch between layouts (Alt-Ctrl or Ctrl+Shift) will affect both the remote machine and my local device. So if I have the local device set up, then remote into another machine and I have to switch the layout, guess what&#8230; now my local device has the wrong layout waiting for me when I close the remote window. Add to that the fact that these key combinations are <em>frequently</em> hit accidentally, switching my layout and making me type gibberish for a word or two before I realize my mistake and correct it.</p>



<ol class="wp-block-list">
<li></li>
</ol>



<h2 class="wp-block-heading">Switching back to Qwerty</h2>



<p>The transition to Dvorak was absolutely <em>not</em> worth it. I figured that out about a year in. Now I&#8217;m several years into it, and for the last year or so I&#8217;ve been asking myself whether switching <em>back</em> to qwerty would be worth it. After all, I&#8217;d have to re-teach myself to type <em>again</em>. And how will this Advantage2 keyboard feel with qwerty key caps put back on it anyway?</p>



<p>I&#8217;ve recently decided to take the plunge and switch back to qwerty. I started today by using a qwerty hardware keyboard with qwerty configured in the OS. Oh, it was painfully slow and error prone. I haven&#8217;t yet replaced the key caps on my Advantage2 keyboard, so I&#8217;m typing Dvorak now for this post. I hesitate&#8230; is <em>this</em> the month I want to take a huge productivity hit to switch back? </p>



<p>The grass is greener on the other side. I know, because I&#8217;ve been there. But just how big is this hill that I&#8217;ll have to climb to get there? Hopefully not as high as it was the last time I passed over it.</p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F08%2Ffrom-qwerty-to-dvorak-to-qwerty&amp;linkname=From%20qwerty%20to%20Dvorak%20to%20qwerty" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F08%2Ffrom-qwerty-to-dvorak-to-qwerty&amp;linkname=From%20qwerty%20to%20Dvorak%20to%20qwerty" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F08%2Ffrom-qwerty-to-dvorak-to-qwerty&amp;linkname=From%20qwerty%20to%20Dvorak%20to%20qwerty" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F08%2Ffrom-qwerty-to-dvorak-to-qwerty&amp;linkname=From%20qwerty%20to%20Dvorak%20to%20qwerty" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F08%2Ffrom-qwerty-to-dvorak-to-qwerty&amp;linkname=From%20qwerty%20to%20Dvorak%20to%20qwerty" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F08%2Ffrom-qwerty-to-dvorak-to-qwerty&#038;title=From%20qwerty%20to%20Dvorak%20to%20qwerty" data-a2a-url="https://blog.nerdbank.net/2024/08/from-qwerty-to-dvorak-to-qwerty" data-a2a-title="From qwerty to Dvorak to qwerty"></a></p>]]></content:encoded>
					
					<wfw:commentRss>https://blog.nerdbank.net/2024/08/from-qwerty-to-dvorak-to-qwerty/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>How I upgraded my NVMe storage with Macrium Reflect Home</title>
		<link>https://blog.nerdbank.net/2024/07/how-i-upgraded-my-nvme-storage-with-macrium-reflect-home</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Wed, 24 Jul 2024 19:09:26 +0000</pubDate>
				<category><![CDATA[Reviews]]></category>
		<category><![CDATA[Bitlocker]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=566</guid>

					<description><![CDATA[I had a computer with a 1TB NVMe storage device. It wasn&#8217;t enough, so I bought a 4TB NVMe device on Amazon Prime Day. I already had an NVMe enclosure and a USB C to USB A converter so I could plug it into my desktop. My goal was to replace the 1TB device with [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I had a computer with a 1TB NVMe storage device. It wasn&#8217;t enough, so I bought a 4TB NVMe device on Amazon Prime Day. I already had <a href="https://www.amazon.com/Sabrent-Type-C-Tool-Free-Enclosure-EC-SNVE/dp/B08RVC6F9Y/ref=sr_1_5?adgrpid=1339205726277592&amp;dib=eyJ2IjoiMSJ9.d6x27WSU-J6jtpdkcQpfdIgFfU1M1JS6SHCCIbkGEIXzClZga6J3kCw3wrc6LdRkh61gkKzpYArah8vHpFfctEGwMhaGbhjQpshGpSNwXc-ktqECHzyUfYg4UFiAgffBcu2TXG09V2o_Ye2i-rHW-gRNnl_9qN4ITiYxPGzOFr78ooA8YWdGbkMdVkKAsaP04gk9zu6r98m_6m4Qo1D1RcnUt5eiTCoQKg9Y3xps3p4.RuWOBOd57YjjyIL379IBDQcAvQ0oaA5pnZPtjbB35WY&amp;dib_tag=se&amp;hvadid=83700428820910&amp;hvbmt=be&amp;hvdev=c&amp;hvlocphy=95292&amp;hvnetw=o&amp;hvqmt=e&amp;hvtargid=kwd-83700700441887%3Aloc-190&amp;hydadcr=18004_10780277&amp;keywords=nvme+enclosure&amp;msclkid=61d65b2c38de168cd9d13d8d742adac4&amp;qid=1721847031&amp;sr=8-5">an NVMe enclosure</a> and a <a href="https://www.amazon.com/gp/product/B0CSJZC5XL">USB C to USB A converter</a> so I could plug it into my desktop. My goal was to replace the 1TB device with the 4TB device, and leave the OS and all data exactly the same.</p>



<p>It turned out I didn&#8217;t need the NVMe enclosure because my motherboard has room for two such devices. I added the new 4TB device to the motherboard and booted Windows from the 1TB device. </p>



<p>Using <a href="https://www.macrium.com/reflectfree">Macrium Reflect Home</a> (a product you purchase or use within a 30 day trial period), I cloned the 1TB drive to the 4TB drive. I did <em>not </em>partition the 4TB drive first, and instead, I cloned all partitions from the 1TB drive to the 4TB. This seemed prudent because Windows presumably created those other 2-3 partitions for UEFI boot reasons. The one change I made in preparing the clone operation was to slide the last (small) partition all the way to the end of the 4TB drive to give room for the main partition to expand to fill the space. This would give me the full extra TBs of storage on the C: drive in Windows rather than force me to create a D: and move data to it.</p>



<p>The clone operation did not automatically enable bitlocker on the target device either, so after the clone operation completed I applied bitlocker immediately. Even before a reboot. This was probably premature, as you&#8217;ll soon see.</p>



<p>Cloning itself was pretty fast, which was great. The clone operation was <em>supposed</em> to leave the target drive in a bootable state. It did not. The rest of this post contains the steps I took to get back into a working condition.</p>



<p>When I used the BIOS to change boot device selection to the 4 TB device, things didn&#8217;t go well. I bounced between getting no bootable device found errors and bitlocker recovery screens. It didn&#8217;t matter how many times I entered the recovery key, it would ask me for it again.</p>



<p>So I used Macrium Reflect Home to create &#8220;Rescue Media&#8221; in the form of a USB drive. This booted, found my two NVMe drives, each with Windows on them. It was pretty easy to use this tool, and it was supposed to let me pick which Windows installations to offer at bootup, and select which drive I would then set in the BIOS as the boot media. In theory, that&#8217;s great. But I had to run this step 3-4 times before it really worked. And ultimately, it left me with a 4TB Windows boot sequence that demanded I enter the bitlocker recovery key on every reboot.</p>



<p>Suspend/resume bitlocker in Windows didn&#8217;t fix it. There was no &#8220;Auto-unlock&#8221; option offered, presumably because this was the OS drive but I don&#8217;t know. In desperation, I <em>removed</em> bitlocker entirely, and rebooted. As expected, no recovery screen challenged the boot up process. So then I tried to re-enable Bitlocker, but I get this weird &#8220;The system cannot find the file specified&#8221; error each time I tried to start the encryption process.</p>



<p>Luckily Lee Qicheng offered <a href="https://answers.microsoft.com/en-us/windows/forum/all/bitlocker-the-system-cannot-find-the-file/76c4223e-6cef-4dc8-9274-e3cc788119ed">a simple and effective solution</a> to the Bitlocker error message on a Microsoft forum I found through a Bing search. That worked brilliantly, and Bitlocker recommended I reboot to test before encrypting the drive, which I accepted. Rebooting did <em>not</em> challenge me for a recovery key. Yay!</p>



<p>So the last thing I need to do is remove my 1TB NVMe and find some new use for it. I&#8217;ll probably use it as a huge USB drive by sliding it into the NVMe enclosure that I already have. </p>



<p>In summary, Macrium Reflect had some tools I needed for the disk cloning. And their software was easy to work with. It&#8217;s just a shame that it left me in a broken state that took a few hours to work through before I was in a ready-to-use state again. If I were to do this again, I might try a competing product to see if it does the job right the first time.</p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-i-upgraded-my-nvme-storage-with-macrium-reflect-home&amp;linkname=How%20I%20upgraded%20my%20NVMe%20storage%20with%20Macrium%20Reflect%20Home" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-i-upgraded-my-nvme-storage-with-macrium-reflect-home&amp;linkname=How%20I%20upgraded%20my%20NVMe%20storage%20with%20Macrium%20Reflect%20Home" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-i-upgraded-my-nvme-storage-with-macrium-reflect-home&amp;linkname=How%20I%20upgraded%20my%20NVMe%20storage%20with%20Macrium%20Reflect%20Home" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-i-upgraded-my-nvme-storage-with-macrium-reflect-home&amp;linkname=How%20I%20upgraded%20my%20NVMe%20storage%20with%20Macrium%20Reflect%20Home" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-i-upgraded-my-nvme-storage-with-macrium-reflect-home&amp;linkname=How%20I%20upgraded%20my%20NVMe%20storage%20with%20Macrium%20Reflect%20Home" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-i-upgraded-my-nvme-storage-with-macrium-reflect-home&#038;title=How%20I%20upgraded%20my%20NVMe%20storage%20with%20Macrium%20Reflect%20Home" data-a2a-url="https://blog.nerdbank.net/2024/07/how-i-upgraded-my-nvme-storage-with-macrium-reflect-home" data-a2a-title="How I upgraded my NVMe storage with Macrium Reflect Home"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to quickly delete old branches in git</title>
		<link>https://blog.nerdbank.net/2024/07/how-to-quickly-delete-old-branches-in-git</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Tue, 16 Jul 2024 15:25:00 +0000</pubDate>
				<category><![CDATA[Software engineering]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[git]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=553</guid>

					<description><![CDATA[As you work in your local clone of a git repo, you tend to create lots of topic branches. These will often get merged online via pull requests, leaving the topic branches in your local clone to languish forever. This clutters up lists of your local branches everywhere, yet deleting them can be tedious and [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>As you work in your local clone of a git repo, you tend to create lots of topic branches. These will often get merged online via pull requests, leaving the topic branches in your local clone to languish forever. This clutters up lists of your local branches everywhere, yet deleting them can be tedious and error prone.</p>



<p>I wrote a tool that automates cleaning up your local branches that have already merged, reducing the tedium to just these commands:</p>



<pre class="wp-block-preformatted">git fetch upstream<br>repo git trim upstream/main</pre>



<p><code>repo</code> is a dotnet CLI command you can install as a global tool:</p>



<pre class="wp-block-preformatted">dotnet tool update -g Nerdbank.DotNetRepoTools</pre>



<p>You can repeat the above command periodically to make sure you have the latest version. Note that the <code>update</code> sub-command used above works for original installation as well as updates.</p>



<p>This <code>repo</code> tool has many other commands that are useful around git, nuget, and other common scenarios. Learn more about it here in its README:</p>



<p><a href="https://github.com/AArnott/DotNetRepoTools?tab=readme-ov-file#readme">AArnott/DotNetRepoTools: A CLI tool with commands to help maintain .NET codebases (github.com)</a></p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-quickly-delete-old-branches-in-git&amp;linkname=How%20to%20quickly%20delete%20old%20branches%20in%20git" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-quickly-delete-old-branches-in-git&amp;linkname=How%20to%20quickly%20delete%20old%20branches%20in%20git" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-quickly-delete-old-branches-in-git&amp;linkname=How%20to%20quickly%20delete%20old%20branches%20in%20git" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-quickly-delete-old-branches-in-git&amp;linkname=How%20to%20quickly%20delete%20old%20branches%20in%20git" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-quickly-delete-old-branches-in-git&amp;linkname=How%20to%20quickly%20delete%20old%20branches%20in%20git" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-quickly-delete-old-branches-in-git&#038;title=How%20to%20quickly%20delete%20old%20branches%20in%20git" data-a2a-url="https://blog.nerdbank.net/2024/07/how-to-quickly-delete-old-branches-in-git" data-a2a-title="How to quickly delete old branches in git"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to safely exfiltrate secrets from Azure Pipelines</title>
		<link>https://blog.nerdbank.net/2024/07/how-to-safely-exfiltrate-secrets-from-azure-pipelines</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Tue, 09 Jul 2024 15:25:23 +0000</pubDate>
				<category><![CDATA[Software engineering]]></category>
		<category><![CDATA[Azure Pipelines]]></category>
		<category><![CDATA[PowerShell]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=539</guid>

					<description><![CDATA[Azure Pipelines does what it can to prevent accidental logging of secrets. As long as you tell it a particular value is a secret (through marking a variable as a secret, or using a logging command), any time the secret may appear in the logs Azure Pipelines will automatically redact it. But when you actually [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Azure Pipelines does what it can to prevent accidental logging of secrets. As long as you tell it a particular value is a secret (through <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-secret-variables?view=azure-devops&amp;tabs=yaml%2Cbash">marking a variable as a secret</a>, or <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&amp;tabs=bash#setsecret-register-a-value-as-a-secret">using a logging command</a>), any time the secret may appear in the logs Azure Pipelines will automatically redact it. But when you actually want or need to export a secret from Azure Pipelines in a secure way, this post tells you one way to do it.</p>



<h2 class="wp-block-heading">Why would you exfiltrate secrets from Azure Pipelines</h2>



<p>Exporting secrets is typically a bad thing. Secrets are meant to be hard to share, and a secret that is exposed to Azure Pipelines but not to other users should typically remain a secret known just to your build agent and pipeline. But on very rare occasions I&#8217;ve found myself needing to recover or export a secret that Azure Pipelines was entitled to but I wasn&#8217;t, although I owned the pipeline.</p>



<p>In such cases, it&#8217;s still important to respect the secret and keep it from prying eyes.</p>



<h2 class="wp-block-heading">Encrypting the secret for export</h2>



<p>Merely logging the secret may fail due to the automatic redaction. While you may be able to defeat this through some trivial encoding tricks on the secret (e.g. base64, hex), doing so also exposes your secrets to the <em>bad guys</em>. The high-level strategy will be to simply log the secret in some encrypted form so that though anyone will be able to observe the encrypted secret, only you will be able to decrypt it.</p>



<p>In my technique I&#8217;m using .NET encryption APIs that are only implemented when running on Windows, so if your secret is only available on linux/mac agents, you&#8217;ll have to switch to a Windows agent first if you&#8217;re going to use the particular code in this blog post.</p>



<p>In studying up for how to do this, I started out with the following blog post, which seemed applicable because I had access to the secret in my pipeline from a powershell script:</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-embed wp-block-embed-embed"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="ohxskdHeud"><a href="https://smsagent.blog/2022/09/23/encrypting-sensitive-data-for-transit-or-rest-with-powershell/">Encrypting Sensitive Data for Transit or Rest with&nbsp;PowerShell</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Encrypting Sensitive Data for Transit or Rest with&nbsp;PowerShell&#8221; &#8212; " src="https://smsagent.blog/2022/09/23/encrypting-sensitive-data-for-transit-or-rest-with-powershell/embed/#?secret=yDfowpfWej#?secret=ohxskdHeud" data-secret="ohxskdHeud" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>But this post kept symmetric and asymmetric encryption as separate functions that couldn&#8217;t interoperate. Symmetric encryption wasn&#8217;t appropriate because that means the pipeline would also have the key to decrypt the secret in order to encrypt it. And if the pipeline YAML contained that key, then anyone else would be able to decrypt the secret too. Asymmetric encryption only worked in a trivial test case because it has a limitation on the length of the secret being encrypted. So I had to combine the two.</p>



<p>I rewrote just about all of the powershell scripts from the above blog post then in order to provide simple cmdlets I could use within the pipeline and from my own machine. The scripts first create a new symmetric key and encrypt the secret with that. The symmetric key is then asymmetrically encrypted, and the encrypted key, the symmetric IV and ciphertext is then logged. On my end, that process is then reversed using my private key in order to recover the original secret.</p>



<p>With the code included at the bottom of the post, I could now take the following steps:</p>



<p>Create a new RSA key and print the public key for checking into the pipeline:</p>



<pre class="wp-block-code"><code>$key = New-AsymmetricKey
&#91;Convert]::tobase64string($key.PublicKey)     </code></pre>



<p>It is <em>critical</em> that I keep that particular Powershell window open until the conclusion of this, because the <code>$key</code> is needed later to decrypt the logged, encrypted secret. I could also have serialized and deserialized the key later if needed, but keeping the window open is easier.</p>



<p>Then I added the EncryptionTools.ps1 file to the repo, imported it into a powershell context, and encrypted the secret. In my case the secret was a text access token. The last step prints out a JSON blob containing the ciphertext and other details I need:</p>



<pre class="wp-block-code"><code>. "$(System.DefaultWorkingDirectory)/azure-pipelines/EncryptionTools.ps1"
$PublicKey = 'UlNBMQAMAAADAAAA...UA7VWDL2d'
$PublicKey = &#91;Convert]::FromBase64String($PublicKey)
$secret = &#91;Text.Encoding]::UTF8.GetBytes($accessToken)
Encrypt-Data -Data $secret -PublicKey $PublicKey | ConvertTo-Json</code></pre>



<p>After running the pipeline, I got JSON like this in my Azure Pipeline console log:</p>



<pre class="wp-block-code"><code>{
  "EncryptedKey": "TjE9Av7Z...mfCz",
  "IV": "2BtjqsymGwFoEuAIyAL3lQ==",
  "Ciphertext": "uPm3lx...Bge5OCHiI="
}</code></pre>



<p>I then imported the logged JSON and decrypted the secret like this:</p>



<pre class="wp-block-code"><code>$packet = '{ "EncryptedKey": ... }' | ConvertFrom-Json
$secret = Decrypt-Data -EncryptedPacket $packet -PrivateKey $key.PrivateKey
$accessToken = &#91;Text.Encoding]::UTF8.GetString($secret)</code></pre>



<p>I now have the access token from Azure Pipelines, in such a way that it would be impossible for anyone except me to decrypt it. In my case, I could now run my local REST API tests far more quickly than I could do by running the pipeline over and over again.</p>



<p>The code that makes the above Powershell script work is below:</p>



<script src="https://gist.github.com/AArnott/478f4385eea29e1b13c21d4b6e29cfd7.js"></script>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-safely-exfiltrate-secrets-from-azure-pipelines&amp;linkname=How%20to%20safely%20exfiltrate%20secrets%20from%20Azure%20Pipelines" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-safely-exfiltrate-secrets-from-azure-pipelines&amp;linkname=How%20to%20safely%20exfiltrate%20secrets%20from%20Azure%20Pipelines" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-safely-exfiltrate-secrets-from-azure-pipelines&amp;linkname=How%20to%20safely%20exfiltrate%20secrets%20from%20Azure%20Pipelines" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-safely-exfiltrate-secrets-from-azure-pipelines&amp;linkname=How%20to%20safely%20exfiltrate%20secrets%20from%20Azure%20Pipelines" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-safely-exfiltrate-secrets-from-azure-pipelines&amp;linkname=How%20to%20safely%20exfiltrate%20secrets%20from%20Azure%20Pipelines" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-safely-exfiltrate-secrets-from-azure-pipelines&#038;title=How%20to%20safely%20exfiltrate%20secrets%20from%20Azure%20Pipelines" data-a2a-url="https://blog.nerdbank.net/2024/07/how-to-safely-exfiltrate-secrets-from-azure-pipelines" data-a2a-title="How to safely exfiltrate secrets from Azure Pipelines"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to set up custom Azure Pipelines agents</title>
		<link>https://blog.nerdbank.net/2024/07/how-to-set-up-custom-azure-pipelines-agents</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Tue, 02 Jul 2024 15:25:00 +0000</pubDate>
				<category><![CDATA[Software engineering]]></category>
		<category><![CDATA[Azure Pipelines]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=522</guid>

					<description><![CDATA[Azure Pipelines and GitHub Actions offer generous compute time for OSS repos PR and CI builds. But when your repo is private, the economics change dramatically. For Azure Pipelines as of the time of this writing, the prices are $40 USD/month per parallel job, with the first 1800 minutes free. GitHub Actions prices are far [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Azure Pipelines and GitHub Actions offer generous compute time for OSS repos PR and CI builds. But when your repo is private, the economics change dramatically. <a href="https://azure.microsoft.com/en-us/pricing/details/devops/azure-devops-services/">For Azure Pipelines</a> as of the time of this writing, the prices are $40 USD/month per parallel job, with the first 1800 minutes free. <a href="https://github.com/pricing/calculator">GitHub Actions</a> prices are far more complicated and you pay by the minute, with macOS agents being very expensive. So what can you do when you&#8217;re developing a closed source project to help keep costs low? Host your own agents!</p>



<p>This post will focus on hosting your own agents on Azure Pipelines. Private agents aren&#8217;t always free either (interestingly). In fact, you pay $15 per parallel job for the privilege of providing your own hardware while Azure Pipelines coordinates the work. The first private agent is free. Visual Studio Enterprise subscribers get 16 private parallel jobs allocated at no extra cost.</p>



<p>There&#8217;s another potential benefit to hosting your own agents: much faster builds. Some of my 25 minute jobs dropped to just 3-5 minutes by moving to private hardware. This came from a combination of my hardware being faster than the slow machines they offer for free, and because my machines aren&#8217;t totally recycled after each job, so prerequisites and caches are left on the machine between jobs, allowing subsequent jobs to do less work.</p>



<p>In the migration to a private agent pool that I just finished, I had agents for all 3 OSs. Your project may not need agents for all 3 OSs though, so feel free to skip to the sections below that are relevant to you. The short pitch though is that although it took a lot of work to figure out how to do it, it isn&#8217;t a lot of work to do it. So I hope to save you a bunch of time by giving you the recipes.</p>



<p>Azure Pipelines offers the agent software to install on each of the OSs, and getting the software to run and register itself with your Azure DevOps account as an available agent is the easy part. The tricky part is getting a stable system, where the agents have all the software your pipelines will need, because while Microsoft publishes the list of all software installed on the build agents, you have to install it yourself. Or at least the subset of the software that your pipelines require.</p>



<p>For all private agents, keep in mind the security implications of running builds of arbitrary code. If you trust the code, and you don&#8217;t build pull requests from strangers, you may consider waiving the security concerns. This blog post is about my experience setting up agents for a private, trusted code base. I made some decisions that would be very unwise if I were building OSS software that accepted outside pull requests.</p>



<p>While I am pleased to share my hard-learned methods here, I freely admit there is a lot more to learn for better setup on each OS. If you know how to improve on these methods, please comment and I&#8217;ll be delighted to update these instructions.</p>



<h2 class="wp-block-heading">Windows agents</h2>



<p><a href="https://github.com/MicrosoftDocs/azure-devops-docs/blob/main/docs/pipelines/agents/docker.md#windows">Microsoft published a great doc</a> on running your Windows build agent inside a Windows docker container. In my case however, I wanted my Windows machine to be able to create <em>linux</em> containers, and the docker software for Windows can only be switched between Windows and linux container modes such that you cannot have a mixture of containers of both OSs. </p>



<p>So I opted to use Hyper-V to create a Windows VM on which I would run the agent directly. I used the Quick Start option to create a VM based on the &#8220;Development VM&#8221; image.</p>



<p>Additional software I installed:</p>



<ul class="wp-block-list">
<li><a href="https://www.rust-lang.org/learn/get-started">Rust</a></li>



<li><a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4#install-powershell-using-winget-recommended">PowerShell on Windows &#8211; PowerShell | Microsoft Learn</a></li>



<li><a href="https://visualstudio.microsoft.com/">Visual Studio</a>, with several workloads including C++ so that rust can find the native toolchain it requires.</li>



<li><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/windows-agent?view=azure-devops">Azure Pipelines build agent</a></li>
</ul>



<p>I then had to run the agent interactively so that it ran under the same user account that I installed rust on, so that builds could find the rust toolchain.</p>



<p>I would have preferred to run the agent as a service so that on reboot it automatically starts, but I didn&#8217;t know how to get rust to work in that environment.</p>



<h2 class="wp-block-heading">Linux agents</h2>



<p><a href="https://github.com/MicrosoftDocs/azure-devops-docs/blob/main/docs/pipelines/agents/docker.md#linux">Microsoft published a great doc</a> on running your Linux build agent inside a Linux docker container. In my setup, I started with these steps and then evolved to enable docker-in-docker so that my pipelines could run <code>cross build</code> (a cargo build step that uses docker containers).</p>



<p>I used Hyper-V&#8217;s Quick Create to create an Ubuntu 22 VM on my Windows machine.</p>



<p>I am using a linux container for the build agent, so I didn&#8217;t need to install any build tools onto the VM itself. Here is the command I used to install a few useful tools though:</p>



<pre class="wp-block-preformatted">sudo apt install -y openssh-server docker.io net-tools screen</pre>



<p>I then configured SSH so I could remote in as needed:</p>



<pre class="wp-block-preformatted">sudo systemctl enable ssh<br>sudo ufw allow ssh</pre>



<p>I also reconfigured SSH to deny password-based authentication, as I prefer the security and convenience of key-based auth: <a href="https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server">How To Configure SSH Key-Based Authentication on a Linux Server | DigitalOcean</a></p>



<p>Because Hyper-V Quick Create gave me a tiny (12GB) volume for my VM&#8217;s disk, I had to enlarge it. I shut the VM down and deleted the snapshot that Hyper-V automatically created. That allowed me to Edit the .vhdx to be much larger. I then booted the VM and had to <a href="https://superuser.com/a/1732294/3354">run commands documented here</a> to get the file system to expand to fill the enlarged virtual hard drive.</p>



<p>Next comes the magic: docker-in-docker. I intend to run the build agent itself in a docker container. Some build jobs require creating docker containers, which doesn&#8217;t work by default. A lot of documentation out there talks about the security risks of enabling docker-in-docker that come from mounting docker.sock from the host into a container, with good reason. But I found another way, courtesy of this excellent article that outlines the options: <a href="https://devopscube.com/run-docker-in-docker/">How To Run Docker In Docker Container [3 Methods Explained] (devopscube.com)</a>.</p>



<p><a href="https://github.com/nestybox/sysbox/blob/master/docs/user-guide/install-package.md#installing-sysbox">By installing sysbox</a> and slightly adjusting my dockerfile and how I launch it, my build agent container can now spawn <em>child</em> containers, such that the build agent container does <em>not</em> need access to manipulate top-level containers or the rest of the linux VM. After installing sysbox, I specifically:</p>



<ol class="wp-block-list">
<li>Installed <code>docker.io</code> onto my build agent docker image via <code>apt</code>.</li>



<li>Added <code>--runtime=sysbox-runc</code> to <code>docker run</code> the command line I use to start the build agent container.</li>



<li>Modified the <code>entrypoint</code> in the dockerfile to run <code>dockerd &gt; /var/log/dockerd.log 2&gt;&amp;1 &amp;</code> before launching the AzP <code>start.sh</code> script.</li>
</ol>



<p>You can see the whole recipe with dockerfile and scripts here:</p>



<script src="https://gist.github.com/AArnott/8a130aae1e1d74638ae240e0941ab8f1.js"></script>



<p>I use the <code>build-and-run.sh</code> script on the linux VM to start one or more linux agent containers.</p>



<h2 class="wp-block-heading">macOS agents</h2>



<p>I bought a relatively new Mac Mini for about $500 on eBay. After a low-level reinstall of the OS to verify there would be no remnants of its prior user, I installed the following software:</p>



<ul class="wp-block-list">
<li><a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos?view=powershell-7.4">PowerShell &#8211; PowerShell | Microsoft Learn</a></li>



<li>Rosetta (so that x64 build tools can run) by running this command:<br><code>/usr/sbin/softwareupdate --install-rosetta --agree-to-license</code></li>
</ul>



<p>I then created a <code>buildagent</code> user account, and within it, I installed:</p>



<ul class="wp-block-list">
<li>rust: <code>curl --proto '=https' --tlsv1.2 <a href="https://sh.rustup.rs">https://sh.rustup.rs</a> -sSf | sh</code></li>



<li><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/osx-agent?view=azure-devops">The Azure Pipelines agent</a></li>
</ul>



<p>Finally, I launched the Azure Pipelines build agent software by invoking <code>~/myagent/run.sh</code> within the <code>buildagent</code> user account.</p>



<p>I wish I could have configured this to automatically restart the agent every time the machine restarted, but I don&#8217;t know how to do that. As a result, I have to be careful to not sign <code>buildagent</code> out, and remember to repeat the <code>run.sh</code> command within that account after each restart.</p>



<h2 class="wp-block-heading">Reconfiguring your pipelines to use your private agents</h2>



<p>With your private agents running in your new private pool in your Azure DevOps account, it&#8217;s time to configure your pipelines to use them instead of the public Hosted pool. </p>



<pre class="wp-block-code"><code>pool:
    name: CustomAgents
    demands:
    - Agent.OS -equals Linux
    - HasDockerAccess</code></pre>



<p>With that, your pipeline (or an individual job on your pipeline) will run on your CustomAgents pool, and in particular on your Linux agent. You can also use <code>Windows_NT</code> or <code>Darwin</code> for your Windows or macOS agents, respectively. The <code>HasDockerAccess</code> demand is unique to linux, and ensures that you get a linux agent from your pool that allows docker-in-docker as described earlier. This may not be important to you, and you might leave it out.</p>



<p>Finally, given that you may have some free public Hosted pool time, you could perhaps save money and hardware by having your pipelines dip into multiple agent pools by having some jobs in your pipeline choose the Hosted pool while other jobs use your private agents. Unfortunately, there is no way for a given job to indicate that they&#8217;ll run on either of two queues, whichever is available first.</p>



<p>I found that there were slight disparities between my private agents and the Hosted ones (probably due to differences in installed software) that required a couple slight changes to my pipelines to accommodate, or added software to install on the agent.</p>



<h2 class="wp-block-heading">Summary</h2>



<p>I&#8217;m very happy with the money I&#8217;m saving by hosting my own build agents instead of paying for Hosted ones for my private git repos. I&#8217;m super happy that my pipelines execute much faster as well. My private agents run on some pretty nice hardware in my home. If you didn&#8217;t have the hardware before, I expect buying it is a great investment that will pay for itself within a few months in saved monthly Hosted compute costs, but you should do your own math to see what is right for you.</p>



<p>Also keep in mind that while I have Windows and Linux agents running in distinct VMs on the same hardware, this leads to each build job running slower as they compete for the same hardware resources. You may want to scale out with more hardware and spread your build agents out.</p>



<p>And just as a reminder, I&#8217;m <em>not</em> an expert in this area. I&#8217;m thrilled to have set this up and want to share with others so they can do the same. But no warranties are expressed or implied here. And if you know of improvements that can be made, please comment and I&#8217;ll be happy to update this doc to incorporate your suggestions.</p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-set-up-custom-azure-pipelines-agents&amp;linkname=How%20to%20set%20up%20custom%20Azure%20Pipelines%20agents" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-set-up-custom-azure-pipelines-agents&amp;linkname=How%20to%20set%20up%20custom%20Azure%20Pipelines%20agents" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-set-up-custom-azure-pipelines-agents&amp;linkname=How%20to%20set%20up%20custom%20Azure%20Pipelines%20agents" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-set-up-custom-azure-pipelines-agents&amp;linkname=How%20to%20set%20up%20custom%20Azure%20Pipelines%20agents" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-set-up-custom-azure-pipelines-agents&amp;linkname=How%20to%20set%20up%20custom%20Azure%20Pipelines%20agents" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F07%2Fhow-to-set-up-custom-azure-pipelines-agents&#038;title=How%20to%20set%20up%20custom%20Azure%20Pipelines%20agents" data-a2a-url="https://blog.nerdbank.net/2024/07/how-to-set-up-custom-azure-pipelines-agents" data-a2a-title="How to set up custom Azure Pipelines agents"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>When string literals are better than nameof(id)</title>
		<link>https://blog.nerdbank.net/2024/06/when-magic-strings-are-better-than-nameofid</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Tue, 25 Jun 2024 15:25:00 +0000</pubDate>
				<category><![CDATA[Software engineering]]></category>
		<category><![CDATA[C#]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=490</guid>

					<description><![CDATA[In C# programming, magic strings often get a bad reputation. They are seen as a code smell, something to be avoided. The advent of nameof(x) in C# gave us a great alternative to magic strings in many cases, perhaps most famously for argument exceptions, like this: This is a great use for nameof because if [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>In C# programming, magic strings often get a bad reputation. They are seen as a code smell, something to be avoided. The advent of <code>nameof(x)</code> in C# gave us a great alternative to magic strings in many cases, perhaps most famously for argument exceptions, like this:</p>



<pre class="wp-block-code"><code>if (somearg is null) throw new ArgumentNullException(nameof(someof));</code></pre>



<p>This is a great use for <code>nameof</code> because if <code>somearg</code> is renamed, the string value passed to the exception&#8217;s constructor will change too, which is definitely what you want.</p>



<p>There are times however when a string may match the name of an identifier, but <code>nameof</code> should <em>not</em> be used. Surprised? Read on.</p>



<p>Some constants that you may define have string values that <em>must not change</em>. Consider this example of an HTTP header:</p>



<pre class="wp-block-code"><code>private const string Authorization = nameof(Authorization); // BAD</code></pre>



<p>Here&#8217;s the problem: a rename refactoring command in your IDE (that might be activated wherever the <code>Authorization</code> constant is referenced) won&#8217;t just change the member name; it will change its <em>value</em> as well. And in the case of a protocol magic string, that will break your compliance with the protocol.</p>



<p>For example, suppose you had a reference to this constant:</p>



<pre class="wp-block-code"><code>request.AddHeader(Authorization, "Bearer abc");</code></pre>



<p>And further suppose you decided that <code>AuthorizationHeader</code> would be a nicer name for this constant, so you triggered a rename refactoring on <code>Authorization</code> right there on the reference to obtain this:</p>



<pre class="wp-block-code"><code>request.AddHeader(AuthorizationHeader, "Bearer abc");</code></pre>



<p>Unbeknownst to you or the C# compiler, you&#8217;ve now changed the value of the header itself:</p>



<pre class="wp-block-code"><code>private const string AuthorizationHeader = nameof(AuthorizationHeader); // OOPS</code></pre>



<p>That will break your HTTP code, of course.</p>



<p>The following syntax is therefore more resilient:</p>



<pre class="wp-block-code"><code>private const string Authorization = "Authorization"; // GOOD, resilient code</code></pre>



<p>A rename on the constant will only change the member name and leave the string literal as it should be.</p>



<h2 class="wp-block-heading">What are persisted strings?</h2>



<p>Let&#8217;s review which strings that are better left as string literals and avoid <code>nameof(x)</code>.</p>



<p>If the string value is persisted in any way (written to disk, the network, or saved in another assembly) aside from the constant declaration itself, it should remain as a string literal because changes to these values would likely break functionality if they were to be changed in the program while the persisted form or other programs retained the original value. Here are a few examples of when to avoid <code>nameof</code>:</p>



<ul class="wp-block-list">
<li>Protocols: As in the example above, communication protocols are between 2 or more programs, typically running on different processes or machines. They each have a copy of the constant that they are going to read or write. If you change your copy, you haven&#8217;t changed theirs, and communication will fail.</li>



<li>Database schemas: Databases persist column and table names and expect exact matches for subsequent access. A renamed C# identifier doesn&#8217;t rename the database entity.</li>



<li>Embeddable strings: The C# compiler will sometimes embed a <em>copy</em> of a constant string in a referencing assembly. Attribute values are a classic case for this. If your assembly declares<br><code>public const string MyConstant = nameof(MyConstant);</code><br>You might feel safe because if you change the value of the constant, at runtime all assemblies that reuse your constant will get the new value. But think again, because <em>this</em> will copy the value of the string into the assembly that uses the attribute:<br><code>[SomeAttribute(YourAssembly.MyConstant)]</code><br><code>class TheirClass { }</code><br>To follow assembly metadata rules, this becomes in the metadata:<br><code>[SomeAttribute("MyConstant")]</code><br>So that if you later change your constant to:<br><code>public const string MyCoolConstant = nameof(MyCoolConstant);</code><br>The referencing assembly will still have <code>"MyConstant"</code> in the attribute and it will not match.</li>
</ul>



<h2 class="wp-block-heading">Conclusion</h2>



<p>While magic strings can be a source of errors if used improperly, they can also provide significant benefits in certain situations. When used correctly, they can make your code more readable, maintainable, and robust. The <code>nameof()</code> operator in C# provides additional safety and convenience, particularly when you want the string to track the name of the type or member. However, in cases where the string is defined by a specification and should not change, using <code>nameof()</code> can be hazardous. As with any tool, the key is to understand when and how to use it effectively.</p>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F06%2Fwhen-magic-strings-are-better-than-nameofid&amp;linkname=When%20string%20literals%20are%20better%20than%20nameof%28id%29" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F06%2Fwhen-magic-strings-are-better-than-nameofid&amp;linkname=When%20string%20literals%20are%20better%20than%20nameof%28id%29" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F06%2Fwhen-magic-strings-are-better-than-nameofid&amp;linkname=When%20string%20literals%20are%20better%20than%20nameof%28id%29" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F06%2Fwhen-magic-strings-are-better-than-nameofid&amp;linkname=When%20string%20literals%20are%20better%20than%20nameof%28id%29" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F06%2Fwhen-magic-strings-are-better-than-nameofid&amp;linkname=When%20string%20literals%20are%20better%20than%20nameof%28id%29" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F06%2Fwhen-magic-strings-are-better-than-nameofid&#038;title=When%20string%20literals%20are%20better%20than%20nameof%28id%29" data-a2a-url="https://blog.nerdbank.net/2024/06/when-magic-strings-are-better-than-nameofid" data-a2a-title="When string literals are better than nameof(id)"></a></p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>To buy and to hold</title>
		<link>https://blog.nerdbank.net/2024/02/to-buy-and-to-hold</link>
		
		<dc:creator><![CDATA[Andrew Arnott]]></dc:creator>
		<pubDate>Thu, 22 Feb 2024 14:57:07 +0000</pubDate>
				<category><![CDATA[Cryptocurrency]]></category>
		<category><![CDATA[zcash]]></category>
		<guid isPermaLink="false">https://blog.nerdbank.net/?p=467</guid>

					<description><![CDATA[Most folks will just buy and hold a cryptocurrency (often referred to as HODLing), waiting for someone else to drive the price up so they can make money. When the price doesn&#8217;t go up, they whine and moan that the coin&#8217;s creators are crooked cheats. I find it ironic that so many of these same [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Most folks will just buy and hold a cryptocurrency (often referred to as HODLing), waiting for <em>someone else </em>to drive the price up so they can make money. When the price doesn&#8217;t go up, they whine and moan that the coin&#8217;s creators are crooked cheats.</p>



<p>I find it ironic that so many of these same folks who argue the merits of decentralized currency then get mad at the &#8216;central&#8217; figures behind the coin for not somehow magically driving the price up, <em>after</em> these folks buy into it.</p>



<p>Getting rich quick isn&#8217;t among my primary motivators for evangelizing cryptocurrencies. I want to see cryptocurrency take over the economy as the primary form of money, while supporting legacy payments for the phone-less. Debit cards with chips could perhaps one day make phones unnecessary. </p>



<p>Honestly, by the time this vision is reached, I&#8217;ll be surprised if <em>any</em> of the cryptocurrencies currently in existence are still around, because none of them measure up to the privacy, scaling and speed that the economy requires. But only by engaging in the cryptocurrencies we have today can we push the economy toward embracing them and motivating continued innovation in that area.</p>



<p>To folks who want to actually <em>contribute</em> to their favorite cryptocurrencies rather than just HODL, here are a few things that I expect would help:</p>



<ol class="wp-block-list">
<li>Accept cryptocurrencies at your business. </li>



<li>Go out of your way to patronize businesses that accept cryptocurrency, even if you wouldn&#8217;t otherwise shop there. Evangelize to other businesses you patronize to accept cryptocurrency.</li>



<li>Write software that improves the experience for handling cryptocurrency, particularly for businesses.</li>



<li>Lobby your government representatives to enact legislation that promotes cryptocurrencies. In the U.S. particularly, ask them to legislate that cryptocurrencies should be considered <em>currencies</em> instead of <em>securities</em> for tax purposes, and to write any other laws necessary to help businesses feel comfortable transacting in them. Emphasize the importance of privacy coins.</li>
</ol>
<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F02%2Fto-buy-and-to-hold&amp;linkname=To%20buy%20and%20to%20hold" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F02%2Fto-buy-and-to-hold&amp;linkname=To%20buy%20and%20to%20hold" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F02%2Fto-buy-and-to-hold&amp;linkname=To%20buy%20and%20to%20hold" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F02%2Fto-buy-and-to-hold&amp;linkname=To%20buy%20and%20to%20hold" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_pocket" href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F02%2Fto-buy-and-to-hold&amp;linkname=To%20buy%20and%20to%20hold" title="Pocket" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Fblog.nerdbank.net%2F2024%2F02%2Fto-buy-and-to-hold&#038;title=To%20buy%20and%20to%20hold" data-a2a-url="https://blog.nerdbank.net/2024/02/to-buy-and-to-hold" data-a2a-title="To buy and to hold"></a></p>]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
