<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.carlinscott.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.carlinscott.com/" rel="alternate" type="text/html" /><updated>2026-09-18T17:15:02+00:00</updated><id>https://www.carlinscott.com/feed.xml</id><title type="html">Carlin Scott</title><subtitle>Professional blog of Carlin Scott, a Senior Software Engineer working from the Silicon Forest.</subtitle><author><name>Carlin Scott</name></author><entry><title type="html">My GPT-3 AI copilot needs an AI copilot</title><link href="https://www.carlinscott.com/2026/09/18/2023-04-05-github-copilot-for-gpt3.html" rel="alternate" type="text/html" title="My GPT-3 AI copilot needs an AI copilot" /><published>2026-09-18T17:15:02+00:00</published><updated>2026-09-18T17:15:02+00:00</updated><id>https://www.carlinscott.com/2026/09/18/2023-04-05-github-copilot-for-gpt3</id><content type="html" xml:base="https://www.carlinscott.com/2026/09/18/2023-04-05-github-copilot-for-gpt3.html"><![CDATA[<p>I had this interesting conversation with <a href="https://marketplace.visualstudio.com/items?itemName=genieai.chatgpt-vscode">Genie AI</a> that resulted in it changing its recommendation based on feedback I gave it from GitHub Copliot. Genie is using the same GPT-3.5 AI model used by ChatGPT, whereas <a href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot">GitHub Copilot</a> is a highly customized GPT-3 model.</p>

<p>The following sections of this post is the conversation I had with Genie, exported directly from the Genie AI VS Code extension using their “export [conversation] to markdown” option.</p>

<p>What’s notable about my interaction with these two models, is how GitHub copilot intuited the exact block of code I needed on its first try without any input from me. Whereas I had to have a conversation with Genie that resulted in a similar block of code that originally did not work due to my use of Docker Desktop WSL2 integration. GitHub Copilot generates correct code snippets for me about 80% of the time, and I can cycle through alternatives using the <code class="language-plaintext highlighter-rouge">alt + ]</code> shortcut. But if it gets it wrong, I have no way to refine the generated code, other than taking the closest match and refining it in such a way that it generates further recommendations. Genie AI and ChatGPT on the other hand, only generate correct code for me about 10% of the time. But I’m able to refine the code through the chat mechanism which can result in some great code.</p>

<p>So in my opinion, a developer should be using both of these tools because GitHub Copilot is surprisngly accurate and has seemless integration with VS through the same mechanism we’re used to with Intellisense. But when we’re in the ideation stage of building software, or if GitHub Copilot doesn’t understand our intent, then the chat feature of Genie or ChatGPT pays off.</p>

<p>Oh, as a side note, what I was trying to implement was a way to give my Kubernetes hosted asp.net services access to my local Azure CLI so that they could use my dev credentials to access the MS Azure services they needed. This way they’d work the same way as they do when running them from Visual Studio. Unfortunately, what I found is that the WSL compatible Azure CLI executable is some sort of virtual file that Kubernetes doesn’t recognize as a valid file. I got this error when I attempted to deploy this solution to my Docker Desktop Kubernetes cluster:</p>

<blockquote>
  <p>MountVolume.SetUp failed for volume “az-cli” : hostPath type check failed: /run/desktop/mnt/host/c/Program\ Files\ (x86)/Microsoft\ SDKs/Azure/CLI2/wbin/az is not a file</p>
</blockquote>

<p>It indeed is reported as a file by Windows Explorer and WSL2 Ubuntu. But Microsoft has some special virtual files it uses to blend the Windows host environment with the WSL environment. So I’m guessing this is one of those situations, and despite Docker Desktop using the WSL environment, it doesn’t recognize the virtual file as a real file.</p>

<p>If you’re wondering about the path being right, I copied that straight from my WSL hosted Ubuntu environment. Kubernetes also successfully mounted the az-cli (renamed az-config) volume given to me by GitHub Copilot.</p>

<h2 id="you">You</h2>

<p>Does the DefaultAzureCredential class provided by the Azure.Identity library for dotnet use the Azure CLI credentials if available?</p>

<h2 id="genie">Genie</h2>

<p>Yes, the DefaultAzureCredential class provided by the Azure.Identity library for dotnet does use the Azure CLI credentials if available. When the DefaultAzureCredential class attempts to authenticate a given Azure service client object, it first tries to authenticate using the local development environment’s VSCode or Visual Studio authenticated user account. If that doesn’t work, it will try to authenticate using credentials provided by Azure PowerShell modules. Finally, if neither of those approaches works, the DefaultAzureCredential class will check for the presence of an Azure CLI installation and use its credentials to authenticate with Azure.</p>

<h2 id="you-1">You</h2>

<p>Find problems with the following code</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">volumes</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">az-cli</span>
    <span class="na">hostPath</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">~/.azure</span>
</code></pre></div></div>

<h2 id="genie-1">Genie</h2>

<p>The YAML code provided seems to be creating a volume for Kubernetes. However, it contains an issue that may cause errors.</p>

<ul>
  <li>The tilde (~) is used as shorthand to represent the current user’s home directory path in shell environments like bash, but it may not work outside of them. Therefore, using <code class="language-plaintext highlighter-rouge">~/.azure</code> in the hostPath section of the YAML file may cause issues when running on a non-shell environment.</li>
</ul>

<p>To fix this issue, you can replace <code class="language-plaintext highlighter-rouge">~/.azure</code> with the absolute path to the host directory where Azure CLI credentials are stored. For example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>volumes:
  - name: az-cli
    hostPath:
      path: /home/user/.azure #replace with actual absolute path
</code></pre></div></div>

<p>Make sure to replace <code class="language-plaintext highlighter-rouge">/home/user/.azure</code> with the actual absolute path to your Azure CLI credentials directory.</p>

<h2 id="you-2">You</h2>

<p>Is that the correct path when using Docker Desktop for Windows?</p>

<h2 id="genie-2">Genie</h2>

<p>No, the correct path to reference in your YAML file when using Docker Desktop for Windows would be different.</p>

<p>On Windows machines, the default path for Azure CLI credentials is <code class="language-plaintext highlighter-rouge">C:\Users\&lt;username&gt;\.azure</code>. Therefore, you should use the following path in your YAML file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>volumes:
  - name: az-cli
    hostPath:
      path: C:\Users\&lt;username&gt;\.azure #replace with actual username
</code></pre></div></div>

<p>Make sure to replace <code class="language-plaintext highlighter-rouge">&lt;username&gt;</code> with your actual Windows username in the <code class="language-plaintext highlighter-rouge">path</code> value of the YAML file.</p>

<h2 id="you-3">You</h2>

<p>Why did GitHub copilot recommend: /run/desktop/mnt/host/c/Users/<username>/.azure</username></p>

<h2 id="genie-3">Genie</h2>

<p>GitHub Copilot recommended <code class="language-plaintext highlighter-rouge">/run/desktop/mnt/host/c/Users/&lt;username&gt;/.azure</code> as the path for the Azure CLI credentials because it is the location where Docker Desktop for Windows maps the <code class="language-plaintext highlighter-rouge">C:</code> drive of the host machine, i.e., your Windows machine.</p>

<p>Docker Desktop for Windows runs in a lightweight virtual machine (VM) that has its file system isolated from the host machine’s file system. To provide access to files and directories on the host machine, Docker Desktop for Windows uses a special feature called “bind mounts” that map directories from the host file system to directories inside containers.</p>

<p>When you mount a bind mount in Docker Desktop for Windows by referencing a path like <code class="language-plaintext highlighter-rouge">/run/desktop/mnt/host/c/Users/&lt;username&gt;/.azure</code>, Docker will automatically map this path to the correct location on the host machine.</p>

<p>So, while <code class="language-plaintext highlighter-rouge">/run/desktop/mnt/host/c/Users/&lt;username&gt;/.azure</code> might look strange, it would work as expected when used in a Docker container running on Docker Desktop for Windows.</p>]]></content><author><name>Carlin Scott</name></author><summary type="html"><![CDATA[I had this interesting conversation with Genie AI that resulted in it changing its recommendation based on feedback I gave it from GitHub Copliot. Genie is using the same GPT-3.5 AI model used by ChatGPT, whereas GitHub Copilot is a highly customized GPT-3 model. The following sections of this post is the conversation I had with Genie, exported directly from the Genie AI VS Code extension using their “export [conversation] to markdown” option. What’s notable about my interaction with these two models, is how GitHub copilot intuited the exact block of code I needed on its first try without any input from me. Whereas I had to have a conversation with Genie that resulted in a similar block of code that originally did not work due to my use of Docker Desktop WSL2 integration. GitHub Copilot generates correct code snippets for me about 80% of the time, and I can cycle through alternatives using the alt + ] shortcut. But if it gets it wrong, I have no way to refine the generated code, other than taking the closest match and refining it in such a way that it generates further recommendations. Genie AI and ChatGPT on the other hand, only generate correct code for me about 10% of the time. But I’m able to refine the code through the chat mechanism which can result in some great code. So in my opinion, a developer should be using both of these tools because GitHub Copilot is surprisngly accurate and has seemless integration with VS through the same mechanism we’re used to with Intellisense. But when we’re in the ideation stage of building software, or if GitHub Copilot doesn’t understand our intent, then the chat feature of Genie or ChatGPT pays off. Oh, as a side note, what I was trying to implement was a way to give my Kubernetes hosted asp.net services access to my local Azure CLI so that they could use my dev credentials to access the MS Azure services they needed. This way they’d work the same way as they do when running them from Visual Studio. Unfortunately, what I found is that the WSL compatible Azure CLI executable is some sort of virtual file that Kubernetes doesn’t recognize as a valid file. I got this error when I attempted to deploy this solution to my Docker Desktop Kubernetes cluster: MountVolume.SetUp failed for volume “az-cli” : hostPath type check failed: /run/desktop/mnt/host/c/Program\ Files\ (x86)/Microsoft\ SDKs/Azure/CLI2/wbin/az is not a file It indeed is reported as a file by Windows Explorer and WSL2 Ubuntu. But Microsoft has some special virtual files it uses to blend the Windows host environment with the WSL environment. So I’m guessing this is one of those situations, and despite Docker Desktop using the WSL environment, it doesn’t recognize the virtual file as a real file. If you’re wondering about the path being right, I copied that straight from my WSL hosted Ubuntu environment. Kubernetes also successfully mounted the az-cli (renamed az-config) volume given to me by GitHub Copilot. You Does the DefaultAzureCredential class provided by the Azure.Identity library for dotnet use the Azure CLI credentials if available? Genie Yes, the DefaultAzureCredential class provided by the Azure.Identity library for dotnet does use the Azure CLI credentials if available. When the DefaultAzureCredential class attempts to authenticate a given Azure service client object, it first tries to authenticate using the local development environment’s VSCode or Visual Studio authenticated user account. If that doesn’t work, it will try to authenticate using credentials provided by Azure PowerShell modules. Finally, if neither of those approaches works, the DefaultAzureCredential class will check for the presence of an Azure CLI installation and use its credentials to authenticate with Azure. You Find problems with the following code volumes: - name: az-cli hostPath: path: ~/.azure Genie The YAML code provided seems to be creating a volume for Kubernetes. However, it contains an issue that may cause errors. The tilde (~) is used as shorthand to represent the current user’s home directory path in shell environments like bash, but it may not work outside of them. Therefore, using ~/.azure in the hostPath section of the YAML file may cause issues when running on a non-shell environment. To fix this issue, you can replace ~/.azure with the absolute path to the host directory where Azure CLI credentials are stored. For example: volumes: - name: az-cli hostPath: path: /home/user/.azure #replace with actual absolute path Make sure to replace /home/user/.azure with the actual absolute path to your Azure CLI credentials directory. You Is that the correct path when using Docker Desktop for Windows? Genie No, the correct path to reference in your YAML file when using Docker Desktop for Windows would be different. On Windows machines, the default path for Azure CLI credentials is C:\Users\&lt;username&gt;\.azure. Therefore, you should use the following path in your YAML file: volumes: - name: az-cli hostPath: path: C:\Users\&lt;username&gt;\.azure #replace with actual username Make sure to replace &lt;username&gt; with your actual Windows username in the path value of the YAML file. You Why did GitHub copilot recommend: /run/desktop/mnt/host/c/Users//.azure Genie GitHub Copilot recommended /run/desktop/mnt/host/c/Users/&lt;username&gt;/.azure as the path for the Azure CLI credentials because it is the location where Docker Desktop for Windows maps the C: drive of the host machine, i.e., your Windows machine. Docker Desktop for Windows runs in a lightweight virtual machine (VM) that has its file system isolated from the host machine’s file system. To provide access to files and directories on the host machine, Docker Desktop for Windows uses a special feature called “bind mounts” that map directories from the host file system to directories inside containers. When you mount a bind mount in Docker Desktop for Windows by referencing a path like /run/desktop/mnt/host/c/Users/&lt;username&gt;/.azure, Docker will automatically map this path to the correct location on the host machine. So, while /run/desktop/mnt/host/c/Users/&lt;username&gt;/.azure might look strange, it would work as expected when used in a Docker container running on Docker Desktop for Windows.]]></summary></entry><entry><title type="html">Visual Studio can’t use git-lfs properly</title><link href="https://www.carlinscott.com/2026/01/23/git-lfs-vs.html" rel="alternate" type="text/html" title="Visual Studio can’t use git-lfs properly" /><published>2026-01-23T00:00:00+00:00</published><updated>2026-01-23T00:00:00+00:00</updated><id>https://www.carlinscott.com/2026/01/23/git-lfs-vs</id><content type="html" xml:base="https://www.carlinscott.com/2026/01/23/git-lfs-vs.html"><![CDATA[<p>I’ve had this issue with git-lfs git-hooks failing to spawn processes for months:</p>

<blockquote>
  <p>15563255 [main] sh 1473 dofork: child -1 - forked process 50444 died unexpectedly, retry 0, exit code 0xC0000142, errno 11
.git/hooks/pre-push: fork: Resource temporarily unavailable</p>
</blockquote>

<p>Initially, this was caused by Microsoft Defender for Windows. And I resolved it by adding <code class="language-plaintext highlighter-rouge">C:\Program Files\Git</code> to the exclusions list:</p>

<blockquote>
  <p>Add-MpPreference -ExclusionPath “C:\Program Files\Git”</p>
</blockquote>

<p>That fixed it for a while. But then it came back. After messing with exclusion rules, paths, and copying files around so that all git.exe’s installed on my system had a git-lfs.exe available to them. I decided to just reinstall all git applications. And that fixed the issue.</p>]]></content><author><name>Carlin Scott</name></author><category term="Visual Studio" /><category term="git-lfs" /><category term="git" /><summary type="html"><![CDATA[I’ve had this issue with git-lfs git-hooks failing to spawn processes for months: 15563255 [main] sh 1473 dofork: child -1 - forked process 50444 died unexpectedly, retry 0, exit code 0xC0000142, errno 11 .git/hooks/pre-push: fork: Resource temporarily unavailable Initially, this was caused by Microsoft Defender for Windows. And I resolved it by adding C:\Program Files\Git to the exclusions list: Add-MpPreference -ExclusionPath “C:\Program Files\Git” That fixed it for a while. But then it came back. After messing with exclusion rules, paths, and copying files around so that all git.exe’s installed on my system had a git-lfs.exe available to them. I decided to just reinstall all git applications. And that fixed the issue.]]></summary></entry><entry><title type="html">Snyk Code Static Analysis and Custom SQL Injection Mitigation in C#</title><link href="https://www.carlinscott.com/2025/02/28/snyk-code-static-analysis.html" rel="alternate" type="text/html" title="Snyk Code Static Analysis and Custom SQL Injection Mitigation in C#" /><published>2025-02-28T00:00:00+00:00</published><updated>2025-02-28T00:00:00+00:00</updated><id>https://www.carlinscott.com/2025/02/28/snyk-code-static-analysis</id><content type="html" xml:base="https://www.carlinscott.com/2025/02/28/snyk-code-static-analysis.html"><![CDATA[<p>When using Snyk Code static analysis to detect SQL injection vulnerabilities in C#, it is important to note that Snyk Code won’t detect a custom SQL injection mitigation that validates dynamic column names against a Regex unless there is a local Regex variable declaration. The actual Regex can be statically defined elsewhere, but there needs to be a local reference to it in order for Snyk to detect that attack vectors were successfully mitigated.</p>

<h2 id="example-code">Example Code</h2>

<p>Here is an example of the code that demonstrates the issue:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Data.SqlClient</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text.RegularExpressions</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">SqlInjectionMitigation</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Regex</span> <span class="n">ColumnNameRegex</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Regex</span><span class="p">(</span><span class="s">"^[a-zA-Z0-9_]+$"</span><span class="p">);</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">ExecuteQuery</span><span class="p">(</span><span class="kt">string</span> <span class="n">columnName</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(!</span><span class="n">ColumnNameRegex</span><span class="p">.</span><span class="nf">IsMatch</span><span class="p">(</span><span class="n">columnName</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentException</span><span class="p">(</span><span class="s">"Invalid column name"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="kt">string</span> <span class="n">query</span> <span class="p">=</span> <span class="s">$"SELECT </span><span class="p">{</span><span class="n">columnName</span><span class="p">}</span><span class="s"> FROM Users"</span><span class="p">;</span>
        <span class="k">using</span> <span class="p">(</span><span class="n">SqlConnection</span> <span class="n">connection</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlConnection</span><span class="p">(</span><span class="s">"your_connection_string"</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="n">SqlCommand</span> <span class="n">command</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlCommand</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">connection</span><span class="p">);</span>
            <span class="n">connection</span><span class="p">.</span><span class="nf">Open</span><span class="p">();</span>
            <span class="n">SqlDataReader</span> <span class="n">reader</span> <span class="p">=</span> <span class="n">command</span><span class="p">.</span><span class="nf">ExecuteReader</span><span class="p">();</span>
            <span class="k">while</span> <span class="p">(</span><span class="n">reader</span><span class="p">.</span><span class="nf">Read</span><span class="p">())</span>
            <span class="p">{</span>
                <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">reader</span><span class="p">[</span><span class="m">0</span><span class="p">]);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In the above code, the <code class="language-plaintext highlighter-rouge">ColumnNameRegex</code> is defined as a static readonly field. However, Snyk Code static analysis won’t detect the custom SQL injection mitigation because there is no local Regex variable declaration.</p>

<h2 id="solution">Solution</h2>

<p>To ensure that Snyk Code detects the custom SQL injection mitigation, you need to add a local Regex variable declaration. Here is the modified code:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Data.SqlClient</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text.RegularExpressions</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">SqlInjectionMitigation</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">Regex</span> <span class="n">ColumnNameRegex</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Regex</span><span class="p">(</span><span class="s">"^[a-zA-Z0-9_]+$"</span><span class="p">);</span>

    <span class="k">public</span> <span class="k">void</span> <span class="nf">ExecuteQuery</span><span class="p">(</span><span class="kt">string</span> <span class="n">columnName</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// Local Regex variable declaration</span>
        <span class="n">Regex</span> <span class="n">localColumnNameRegex</span> <span class="p">=</span> <span class="n">ColumnNameRegex</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(!</span><span class="n">localColumnNameRegex</span><span class="p">.</span><span class="nf">IsMatch</span><span class="p">(</span><span class="n">columnName</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">ArgumentException</span><span class="p">(</span><span class="s">"Invalid column name"</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="kt">string</span> <span class="n">query</span> <span class="p">=</span> <span class="s">$"SELECT </span><span class="p">{</span><span class="n">columnName</span><span class="p">}</span><span class="s"> FROM Users"</span><span class="p">;</span>
        <span class="k">using</span> <span class="p">(</span><span class="n">SqlConnection</span> <span class="n">connection</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlConnection</span><span class="p">(</span><span class="s">"your_connection_string"</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="n">SqlCommand</span> <span class="n">command</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SqlCommand</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">connection</span><span class="p">);</span>
            <span class="n">connection</span><span class="p">.</span><span class="nf">Open</span><span class="p">();</span>
            <span class="n">SqlDataReader</span> <span class="n">reader</span> <span class="p">=</span> <span class="n">command</span><span class="p">.</span><span class="nf">ExecuteReader</span><span class="p">();</span>
            <span class="k">while</span> <span class="p">(</span><span class="n">reader</span><span class="p">.</span><span class="nf">Read</span><span class="p">())</span>
            <span class="p">{</span>
                <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">reader</span><span class="p">[</span><span class="m">0</span><span class="p">]);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>By adding the local Regex variable declaration <code class="language-plaintext highlighter-rouge">Regex localColumnNameRegex = ColumnNameRegex;</code>, Snyk Code static analysis will now detect the custom SQL injection mitigation and recognize that the attack vectors have been successfully mitigated.</p>]]></content><author><name>Carlin Scott</name></author><category term="Snyk" /><category term="Static Analysis" /><category term="SQL Injection" /><category term="C#" /><category term="Regex" /><summary type="html"><![CDATA[When using Snyk Code static analysis to detect SQL injection vulnerabilities in C#, it is important to note that Snyk Code won’t detect a custom SQL injection mitigation that validates dynamic column names against a Regex unless there is a local Regex variable declaration. The actual Regex can be statically defined elsewhere, but there needs to be a local reference to it in order for Snyk to detect that attack vectors were successfully mitigated.]]></summary></entry><entry><title type="html">Microsoft Edge Browser Sleepy Tabs</title><link href="https://www.carlinscott.com/2022/10/10/edge-sleepy-tabs.html" rel="alternate" type="text/html" title="Microsoft Edge Browser Sleepy Tabs" /><published>2022-10-10T00:00:00+00:00</published><updated>2022-10-10T00:00:00+00:00</updated><id>https://www.carlinscott.com/2022/10/10/edge-sleepy-tabs</id><content type="html" xml:base="https://www.carlinscott.com/2022/10/10/edge-sleepy-tabs.html"><![CDATA[<p>I’ve been using the new(ish) Microsoft Edge Browser for the last couple months, and I must admit I like it more than the competition due its performance and power consumption.</p>

<p>However, I’ve been having trouble with using Microsoft Teams for Web within this browser due to one of the performance and power saving features: <strong>Sleeping Tabs</strong>.</p>

<p>This feature frees up resources for a tab I have not interacted with in the last two hours, pausing notifications from my MS Teams tab. This is of course a huge problem, because I need to be always available on Teams during work hours, and I’ve missed about a dozen messages as a result. But no longer, for I have found the solution to my problem.</p>

<h2 id="disable-sleeping-tabs-for-a-site">Disable Sleeping Tabs for a site</h2>

<p>You can disable Sleeping Tabs for individual sites, or more specifically domains, by following this handy guide: https://www.tenforums.com/tutorials/165266-add-remove-sites-sleeping-tabs-block-list-microsoft-edge.html</p>]]></content><author><name>Carlin Scott</name></author><summary type="html"><![CDATA[I’ve been using the new(ish) Microsoft Edge Browser for the last couple months, and I must admit I like it more than the competition due its performance and power consumption. However, I’ve been having trouble with using Microsoft Teams for Web within this browser due to one of the performance and power saving features: Sleeping Tabs. This feature frees up resources for a tab I have not interacted with in the last two hours, pausing notifications from my MS Teams tab. This is of course a huge problem, because I need to be always available on Teams during work hours, and I’ve missed about a dozen messages as a result. But no longer, for I have found the solution to my problem. Disable Sleeping Tabs for a site You can disable Sleeping Tabs for individual sites, or more specifically domains, by following this handy guide: https://www.tenforums.com/tutorials/165266-add-remove-sites-sleeping-tabs-block-list-microsoft-edge.html]]></summary></entry><entry><title type="html">Using App Icons with Windows</title><link href="https://www.carlinscott.com/2022/08/26/app-icons-windows.html" rel="alternate" type="text/html" title="Using App Icons with Windows" /><published>2022-08-26T00:00:00+00:00</published><updated>2022-08-26T00:00:00+00:00</updated><id>https://www.carlinscott.com/2022/08/26/app-icons-windows</id><content type="html" xml:base="https://www.carlinscott.com/2022/08/26/app-icons-windows.html"><![CDATA[<p>I just learned how to add an icon to my Windows application, and I was surprised how hard it was to put the pieces together from various resources. So here’s my one-stop guide to application icons for Windows developers.</p>

<h2 id="application-icon-windows-forms">Application Icon (Windows Forms)</h2>

<p>Each component with a window can have its icon set in the <code class="language-plaintext highlighter-rouge">InitializeComponent</code> or constructor methods. But there are several ways to add the icon file to the project.</p>

<h5 id="mycomponentcs-initializecomponent">MyComponent.cs InitializeComponent()</h5>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// myicon.ico added to project as embedded resource</span>
<span class="k">this</span><span class="p">.</span><span class="n">Icon</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Drawing</span><span class="p">.</span><span class="nf">Icon</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">MyEntrypointClass</span><span class="p">),</span> <span class="s">"myicon.ico"</span><span class="p">);</span>

<span class="c1">// myicon.ico added to project resource file</span>
<span class="k">this</span><span class="p">.</span><span class="n">Icon</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Drawing</span><span class="p">.</span><span class="nf">Icon</span><span class="p">(</span><span class="n">MyEntrypointClass</span><span class="p">.</span><span class="n">Properties</span><span class="p">.</span><span class="n">Resources</span><span class="p">.</span><span class="n">MyIconKey</span><span class="p">);</span>

<span class="c1">// myicon.ico added to component resource file</span>
<span class="k">this</span><span class="p">.</span><span class="n">Icon</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Drawing</span><span class="p">.</span><span class="nf">Icon</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">MyComponentClass</span><span class="p">),</span> <span class="n">Resources</span><span class="p">.</span><span class="n">MyIconKey</span><span class="p">);</span>

<span class="c1">// myicon.ico added to project as "copy to output directory"</span>
<span class="k">this</span><span class="p">.</span><span class="n">Icon</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Drawing</span><span class="p">.</span><span class="nf">Icon</span><span class="p">(</span><span class="s">"myicon.ico"</span><span class="p">);</span>
</code></pre></div></div>

<p>Any of these methods work fine, but I’ve listed them in order of my preference. I prefer not using a resource file because if I’m not supporting multiple languages, they add unecesary overhead to compilation and probably negatively impact runtime performance.</p>

<p>Normally, I would prefer leaving the file external to the DLL to reduce time it takes to load the DLL; meaning the last option I listed. However, in this case, the icon will most definitely be needed at startup, so embedding it makes the most sense as it’s simpler to manage the deployment with less files.</p>

<h2 id="addremove-programs-icon-msi">Add/Remove Programs Icon (MSI)</h2>

<p>You can set the icon used in Add/Remove Programs in your WiX project WSX file:</p>

<h5 id="productwsx">Product.wsx</h5>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Wix&gt;</span>
  <span class="nt">&lt;Product&gt;</span>
    <span class="nt">&lt;Icon</span> <span class="na">Id=</span><span class="s">"icon.ico"</span> <span class="na">SourceFile=</span><span class="s">"$(var.SolutionDir)\myicon.ico"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;Property</span> <span class="na">Id=</span><span class="s">"ARPPRODUCTICON"</span> <span class="na">Value=</span><span class="s">"icon.ico"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>The installer icon is more complicated to set because it needs to be a banner bitmap, so I didn’t bother figuring that out.</p>]]></content><author><name>Carlin Scott</name></author><category term="C#" /><category term="dotnet" /><category term=".NET" /><category term="Windows" /><category term="WiX" /><category term="MSI" /><summary type="html"><![CDATA[I just learned how to add an icon to my Windows application, and I was surprised how hard it was to put the pieces together from various resources. So here’s my one-stop guide to application icons for Windows developers. Application Icon (Windows Forms) Each component with a window can have its icon set in the InitializeComponent or constructor methods. But there are several ways to add the icon file to the project. MyComponent.cs InitializeComponent() // myicon.ico added to project as embedded resource this.Icon = new System.Drawing.Icon(typeof(MyEntrypointClass), "myicon.ico"); // myicon.ico added to project resource file this.Icon = new System.Drawing.Icon(MyEntrypointClass.Properties.Resources.MyIconKey); // myicon.ico added to component resource file this.Icon = new System.Drawing.Icon(typeof(MyComponentClass), Resources.MyIconKey); // myicon.ico added to project as "copy to output directory" this.Icon = new System.Drawing.Icon("myicon.ico"); Any of these methods work fine, but I’ve listed them in order of my preference. I prefer not using a resource file because if I’m not supporting multiple languages, they add unecesary overhead to compilation and probably negatively impact runtime performance. Normally, I would prefer leaving the file external to the DLL to reduce time it takes to load the DLL; meaning the last option I listed. However, in this case, the icon will most definitely be needed at startup, so embedding it makes the most sense as it’s simpler to manage the deployment with less files. Add/Remove Programs Icon (MSI) You can set the icon used in Add/Remove Programs in your WiX project WSX file: Product.wsx &lt;Wix&gt; &lt;Product&gt; &lt;Icon Id="icon.ico" SourceFile="$(var.SolutionDir)\myicon.ico"/&gt; &lt;Property Id="ARPPRODUCTICON" Value="icon.ico" /&gt; The installer icon is more complicated to set because it needs to be a banner bitmap, so I didn’t bother figuring that out.]]></summary></entry><entry><title type="html">Windows Hyper-V DNS Resolution</title><link href="https://www.carlinscott.com/2022/06/24/hyper-v-dns.html" rel="alternate" type="text/html" title="Windows Hyper-V DNS Resolution" /><published>2022-06-24T00:00:00+00:00</published><updated>2022-06-24T00:00:00+00:00</updated><id>https://www.carlinscott.com/2022/06/24/hyper-v-dns</id><content type="html" xml:base="https://www.carlinscott.com/2022/06/24/hyper-v-dns.html"><![CDATA[<p>I’ve been wondering why I can’t reference my guest OS by DNS name, as well as my host OS. At first I thought it was my DNS server, and there was one oddity there, but mostly it was an issue with the “default” virtual switch provided by Hyper-V. Eventually I learned through reading a dozen forum posts, that I needed to use a “public” virtual switch.</p>

<h2 id="problem">Problem</h2>

<p>Reference Guest/Host by DNS name.</p>

<h2 id="solution">Solution</h2>

<ol>
  <li>Create a public virtual switch:
    <ol>
      <li>Open the Hyper-V Manager</li>
      <li>Under Actions on the right, select “Virtual Switch Manager”</li>
      <li>You should only see one or two Switches:
        <ul>
          <li>Default Switch - an internal switch which allows internet access but uses a private subnet for the VMs</li>
          <li>WSL (optional) - An internal switch which doesn’t allow internew access. Use by the Windows Subsystem for Linux 2 (WSL2)</li>
        </ul>
      </li>
      <li>Select “New virutal network switch”</li>
      <li>Choose External for the type</li>
      <li>Click “Create Virtual Switch”</li>
      <li>OK</li>
    </ol>
  </li>
  <li>Assign the VM to the public virtual switch:
    <ol>
      <li>Select the VM</li>
      <li>Settings…</li>
      <li>Network Adapter</li>
      <li>Virtual switch: External</li>
      <li>OK</li>
    </ol>
  </li>
</ol>

<p>That’s it! You can now make network requests to the host and guest using their hostname.</p>

<h3 id="still-didnt-work">Still Didn’t Work</h3>

<p>If this didn’t work for you, then you’re going to need to ensure that your network has its own DNS server and that it is set as the primary DNS for your network.</p>

<p>Additionally, the DNS server itself should not be configured to forward local DNS queries. It must handle those itself.</p>]]></content><author><name>Carlin Scott</name></author><summary type="html"><![CDATA[I’ve been wondering why I can’t reference my guest OS by DNS name, as well as my host OS. At first I thought it was my DNS server, and there was one oddity there, but mostly it was an issue with the “default” virtual switch provided by Hyper-V. Eventually I learned through reading a dozen forum posts, that I needed to use a “public” virtual switch. Problem Reference Guest/Host by DNS name. Solution Create a public virtual switch: Open the Hyper-V Manager Under Actions on the right, select “Virtual Switch Manager” You should only see one or two Switches: Default Switch - an internal switch which allows internet access but uses a private subnet for the VMs WSL (optional) - An internal switch which doesn’t allow internew access. Use by the Windows Subsystem for Linux 2 (WSL2) Select “New virutal network switch” Choose External for the type Click “Create Virtual Switch” OK Assign the VM to the public virtual switch: Select the VM Settings… Network Adapter Virtual switch: External OK That’s it! You can now make network requests to the host and guest using their hostname. Still Didn’t Work If this didn’t work for you, then you’re going to need to ensure that your network has its own DNS server and that it is set as the primary DNS for your network. Additionally, the DNS server itself should not be configured to forward local DNS queries. It must handle those itself.]]></summary></entry><entry><title type="html">HTTPS Redirects</title><link href="https://www.carlinscott.com/2022/03/11/https-redirects.html" rel="alternate" type="text/html" title="HTTPS Redirects" /><published>2022-03-11T00:00:00+00:00</published><updated>2022-03-11T00:00:00+00:00</updated><id>https://www.carlinscott.com/2022/03/11/https-redirects</id><content type="html" xml:base="https://www.carlinscott.com/2022/03/11/https-redirects.html"><![CDATA[<p>I’ve had to deal with the issue of redirecting a domain on multiple platforms, and it was a pain until today.</p>

<p>Despite the silly name, <a href="https://redirect.pizza">Redirect Pizza</a> is amazing. It does just this one thing, and it does it flawlessly. I had my domain redirected in under 5 minutes.</p>

<h2 id="redirect-pizza">Redirect Pizza</h2>

<p>This service is simply elegant. Their landing page asks where you want to redirect from, and where to. It’s just two input boxes and a submit button. They then ask you to create an account using a variety of popular platforms, or your email address, and then you’re presented with instructions for modifying your DNS record to complete the redirect setup.</p>

<p>It even verified that I had finished my DNS configuration within a minute of my making the change; no page refresh required. Although I did have to refresh the page to see the overall domain status update to completed.</p>

<p>I’m using this service for the blog you’re looking at right now. It used to be hosted on Blogger at https://blog.carlinscott.com, but thanks to Redirect Pizza, it’s hosted here on GitHub Pages.</p>

<h2 id="github-pages">GitHub Pages</h2>

<p>This site is hosted on GitHub Pages, which only provides apex domain and https redirect:</p>

<ol>
  <li>http://carlinscott.com &gt; httpS://carlinscott.com</li>
  <li>https://carlinscott.com &gt; https://www.carlinscott.com</li>
</ol>

<h2 id="aws">AWS</h2>

<p>You can do http &gt; https redirects on most AWS services, but the other kinds of redirects are more difficult.</p>

<p>The biggest issue with AWS is that they have no solution for redirecting an apex (aka bare, naked) domain that doesn’t require you to transfer DNS control to them from your domain registrar (GoDaddy, NameCheap, etc).</p>

<p>I worked around that issue by deploying a public EC2 server to handle the redirect using ngnx. But it has been a pain to keep the Let’s Encrypt cert up to date. I have a cron-job set up to renew the cert every 2 months, but it runs, throws no errors, and doesn’t renew the cert. But when I run it manually, it works perfectly.</p>

<p>That solution costs $6/mo.</p>

<p>If you can forward DNS to AWS, then you can use a Load Balancer Listener Rule to perform whatever redirects you need.</p>

<p>This costs minimum $18/mo, but if you’re using this solution, you probably already needed a load balancer. So it’s basically free.</p>

<h2 id="naked-ssl">Naked SSL</h2>

<p>This service looks if simpler than Redirect Pizza, but it’s not as easy to use, and they provide little to no info about their service without signing up. I used their domain redirect tester without signing up, and it said that it didn’t support my domain without providing an explanation.</p>

<p>I don’t recommend this service.</p>

<h2 id="redirection-io">Redirection IO</h2>

<p>This service is really complicated to use. I spent about an hour trying to set up my free redirect from blog.carlinscott.com to www.carlinscott.com, and I gave up. They require you to write rules to match the entire URL using their weird syntax that I couldn’t figure out. They could have just let me use Regex, or provided a catch-all example, but they didn’t. I also wasn’t sure how to deploy my solution after I figured out the matchers.</p>

<p>I think this service could be useful for admins who are managing huge and complex networks of related websites. Their redirect matching engine is sophisticated and provides custom verification scenarios so that you can ensure that it will work the way you want.</p>

<p>I only recommend looking at this service for enterprise IT architects.</p>

<h2 id="301-redirect-website">301 Redirect Website</h2>

<p>This is a free service that only provides HTTP redirects. So not terribly useful in the age of HTTPS Everywhere.</p>]]></content><author><name>Carlin Scott</name></author><category term="web development" /><category term="administration" /><category term="infrastructure" /><category term="devops" /><summary type="html"><![CDATA[How to redirect an apex domain, or subdomain to a service that doesn't provide redirects.]]></summary></entry><entry><title type="html">Tax Preparation Services</title><link href="https://www.carlinscott.com/2022/03/11/tax-prep-services.html" rel="alternate" type="text/html" title="Tax Preparation Services" /><published>2022-03-11T00:00:00+00:00</published><updated>2022-03-11T00:00:00+00:00</updated><id>https://www.carlinscott.com/2022/03/11/tax-prep-services</id><content type="html" xml:base="https://www.carlinscott.com/2022/03/11/tax-prep-services.html"><![CDATA[<p>Are there any tax preparation services that actually provide useful services? I’ve used five, and none of them import more than my name and address from my previous tax filings. It seems like all they do is provide a user interface on top of the IRS tax forms that’s marginally better than filling them out myself. I think I might be better off making my own Excel or Google spreadsheets for the tax forms I use, so that I can just copy and update them each year.</p>

<p>I will continue to use the tax prep services because of their audit protection insurance. But come on, can’t they pre-fill some of my tax return?</p>

<h2 id="features-id-like-to-see">Features I’d like to see</h2>

<h3 id="asset-depreciation-history">Asset Depreciation History</h3>

<p>The most useful and easy to implement feature that I’d like to see from any of the tax services, is importing my depreciating assets from previous tax returns. I have to enter their depreciation history each year, so it’s a simply a clone of previous years, plus what I’m going to depreciate this year. And the tax service can suggest the expected depreciation for this year while giving me the option to modify it.</p>

<h3 id="automatic-imports">Automatic Imports</h3>

<p>Most of the info we fill in on our taxes, is available online on various websites. I’ve seen some progress on this front from TurboTax, but it’s pretty minimal. Last time I checked, they only imported a W2 from ADP. And that process was more complicated than just entering the info from the W2 I was sent in the mail.</p>

<p>Services they could import data from:</p>
<ul>
  <li>Investment and retirement account providers
    <ul>
      <li>Services: Fidelity, Vanguard, Prudential</li>
      <li>Tax related accounts: 401k, IRA, Roth IRA, Realized gains/losses from personal investments</li>
    </ul>
  </li>
  <li>Banks
    <ul>
      <li>Basically all banks have online account access, and many provide expense classifications. So they could import all my charitable donations and deductible expenses and depreciable asset purchases.</li>
      <li>They could also run some analyses to determine if I’m forgetting to include misc income or if I missed classifying an expense as tax deductible.</li>
    </ul>
  </li>
  <li>W2 from ADP and Workplace</li>
  <li>Sales
    <ul>
      <li>Income from Etsy, RedBubble, Threaded, etc</li>
    </ul>
  </li>
</ul>

<h2 id="services-ive-used">Services I’ve Used</h2>

<ul>
  <li>Credit Karma taxes / CashApp Taxes<br />
are free, and have a pretty good interface. But they don’t provide as much help and extra services as the two paid service I’ve tried. I do like their interface better than TaxACT though.</li>
  <li>TaxACT</li>
  <li>TurboTax<br />
has the best features, but they’re also the most expensive by a large amount; they also use deceptive, almost scam like marketing tactics, and they’re a major reason why the IRS doesn’t just send us a bill.</li>
</ul>]]></content><author><name>Carlin Scott</name></author><summary type="html"><![CDATA[Are there any tax preparation services that actually provide useful services? I’ve used five, and none of them import more than my name and address from my previous tax filings. It seems like all they do is provide a user interface on top of the IRS tax forms that’s marginally better than filling them out myself. I think I might be better off making my own Excel or Google spreadsheets for the tax forms I use, so that I can just copy and update them each year. I will continue to use the tax prep services because of their audit protection insurance. But come on, can’t they pre-fill some of my tax return? Features I’d like to see Asset Depreciation History The most useful and easy to implement feature that I’d like to see from any of the tax services, is importing my depreciating assets from previous tax returns. I have to enter their depreciation history each year, so it’s a simply a clone of previous years, plus what I’m going to depreciate this year. And the tax service can suggest the expected depreciation for this year while giving me the option to modify it. Automatic Imports Most of the info we fill in on our taxes, is available online on various websites. I’ve seen some progress on this front from TurboTax, but it’s pretty minimal. Last time I checked, they only imported a W2 from ADP. And that process was more complicated than just entering the info from the W2 I was sent in the mail. Services they could import data from: Investment and retirement account providers Services: Fidelity, Vanguard, Prudential Tax related accounts: 401k, IRA, Roth IRA, Realized gains/losses from personal investments Banks Basically all banks have online account access, and many provide expense classifications. So they could import all my charitable donations and deductible expenses and depreciable asset purchases. They could also run some analyses to determine if I’m forgetting to include misc income or if I missed classifying an expense as tax deductible. W2 from ADP and Workplace Sales Income from Etsy, RedBubble, Threaded, etc Services I’ve Used Credit Karma taxes / CashApp Taxes are free, and have a pretty good interface. But they don’t provide as much help and extra services as the two paid service I’ve tried. I do like their interface better than TaxACT though. TaxACT TurboTax has the best features, but they’re also the most expensive by a large amount; they also use deceptive, almost scam like marketing tactics, and they’re a major reason why the IRS doesn’t just send us a bill.]]></summary></entry><entry><title type="html">Executing raw SQL with EntityFramework</title><link href="https://www.carlinscott.com/2020/02/executing-raw-sql-with-entityframework.html" rel="alternate" type="text/html" title="Executing raw SQL with EntityFramework" /><published>2020-02-06T00:00:00+00:00</published><updated>2020-02-06T00:00:00+00:00</updated><id>https://www.carlinscott.com/2020/02/executing-raw-sql-with-ef</id><content type="html" xml:base="https://www.carlinscott.com/2020/02/executing-raw-sql-with-entityframework.html"><![CDATA[<p>Throughout the interwebs, you will find a solution for executing raw SQL using EntityFramework:</p>

<pre><code class="language-C#">dbContext.DbSet.FromSql(queryString)
</code></pre>

<p>This method does not actually execute raw SQL. It interprets the SQL query and then attempts to generate a LINQ query from it. This will fail if you have SQL methods in your query string. It can also fail for some parameter types. Finally, even if it works, it will wrap your query in another query in order to make it compatible with LINQ, whether or not you actually use LINQ.</p>

<p>In general, this is not a useful method in my opinion.</p>

<p>If you do not need to retrieve data, there’s another method available that actually executes raw SQL:</p>

<pre><code class="language-C#">dbContext.DbSet.ExecuteSqlCommandAsync(queryString);
</code></pre>

<p>This command returns an int reflecting the number of records affected.</p>

<p>But what if I want to perform a SELECT query using raw SQL? Well, EntityFramework has no option for you. However, you are in luck because there’s another library that can be used with EF to accomplish your goal. It’s called Dapper.</p>

<p>Dapper provides several raw SQL execution methods that can be applied to an IDbConnection. And you can access the IDbConnection from EF, like so:</p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Dapper</span><span class="p">;</span>
<span class="n">IDbConnection</span> <span class="n">dbConnection</span> <span class="p">=</span> <span class="n">dbContext</span><span class="p">.</span><span class="n">Database</span><span class="p">.</span><span class="nf">GetPgSqlConnection</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">results</span> <span class="p">=</span> <span class="k">await</span> <span class="n">dbConnection</span><span class="p">.</span><span class="nf">QueryFirstOrDefaultAsync</span><span class="p">(</span><span class="n">queryString</span><span class="p">);</span>
</code></pre></div></div>

<p>The QueryFirstOrDefaultAsync method is an extension method provided by Dapper.</p>]]></content><author><name>Carlin Scott</name></author><category term="dotnet core" /><category term="EntityFramework" /><category term="EF" /><category term="EF Core" /><category term="SQL" /><category term="postgres" /><category term="LINQ" /><summary type="html"><![CDATA[How to execute raw SQL while using EntityFramework, by using Dapper instead.]]></summary></entry><entry><title type="html">Hiding pre-production ASP.NET sites from robots (search engines, web crawlers)</title><link href="https://www.carlinscott.com/2020/02/hiding-pre-production-aspnet-sites-from.html" rel="alternate" type="text/html" title="Hiding pre-production ASP.NET sites from robots (search engines, web crawlers)" /><published>2020-02-04T00:00:00+00:00</published><updated>2020-02-04T00:00:00+00:00</updated><id>https://www.carlinscott.com/2020/02/hiding-pre-production-aspnet-sites-from</id><content type="html" xml:base="https://www.carlinscott.com/2020/02/hiding-pre-production-aspnet-sites-from.html"><![CDATA[<p>As software developers, we love to put things out into the world for people to see and play with. Sometimes there’s a manager behind us, or a business analyst in front of us asking us to hurry up.</p>

<p>This drive to deploy can lead to us skipping some important steps, such as preventing search engines from publishing our test and demo environments. To avoid this, we can employ two different tactics.</p>

<h2 id="1-robotstxt">1. robots.txt</h2>

<p>This is a file that has been around for ages with a singular purpose; to tell robots what to do when they encounter your website. Not all robots listen to our instructions, but we don’t necessarily need to worry about those ones. The most problematic robots behave well but have a big impact on us. These robots are search engine web crawlers.</p>

<p>When a search engine crawls your website, it publishes your website to people searching for things related to your site. This may not impact you, but you could end up with a real customer accidentally visiting your beta or test site on accident. You may also end up with a lot more curious people visiting your test site than you’d like.</p>

<h3 id="aspnet-robotstxtcshtml">ASP.NET robots.txt.cshtml</h3>

<p>If you’re using a modern version of ASP.NET, you can create a simple Razor Page that will tell robots to go elsewhere for your non-production sites.</p>

<p>This file should be placed with your other Razor Pages in the Pages directory in your ASP.NET website project. You only need to have MVC enabled in Startup.cs to use this, and it doesn’t require any controller logic because Razor Pages are magical.</p>

<p>What this Razor Page does, is generate a robots.txt file in your website’s root folder. For non-production environments, the robots.txt file tells robots to avoid interacting with the entire site. For production, it tells them to avoid crawling the /hidden/path, which is just a placeholder for any routes on your site that you don’t want to be indexed by a search engine.</p>

<script src="https://gist.github.com/08a8c3341b0ee85a9ffd350ba51ae056.js?file=robots.cshtml"> </script>

<h2 id="2-web-access-firewall-waf">2. Web Access Firewall (WAF)</h2>

<p>You may be more worried about security bugs on your non-production websites, than them showing up in search results. You may also have an admin portal that you only want accessible from your coworkers and business partners.</p>

<p>The best way to protect sensitive areas on your website is definitely not a robots.txt. For one thing, it doesn’t prevent a person or a robot from entering the restricted area, rather it just tells them that they souldn’t. It’s like a realy low-key keep out sign. The other problem with highlighting a restricted route with robots.txt is that it highlights the sensitive area for hackers to exploit.</p>

<p>A WAF can restrict access to sections of your website to specific blocks of IPs. It is quite common for companies to have a section of their website only available to people on their network. To accomplish this, they create a WAF routing rule that only allows their block of IP addresses from accessing certain routes.</p>

<p>I will not go into the details of doing this as it is dependent on where you’re hosting your website, and the web host platform you are using. However, I think the terminology I have provided will help you find what you need on the internet.</p>]]></content><author><name>Carlin Scott</name></author><category term="asp.net core" /><category term="razor pages" /><category term="security" /><summary type="html"><![CDATA[How to create robots.txt using ASP.NET Core Razor Pages to hide pages and sites from search results]]></summary></entry></feed>