<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[Damir's Corner]]></title>
        <description><![CDATA[Notes from Daily Encounters with Technology]]></description>
        <link>https://www.damirscorner.com</link>
        <generator>RSS for Node</generator>
        <lastBuildDate>Fri, 29 May 2026 07:22:52 GMT</lastBuildDate>
        <atom:link href="https://www.damirscorner.com/blog/posts/rss.xml" rel="self" type="application/rss+xml"/>
        <author><![CDATA[Damir Arh]]></author>
        <pubDate>Fri, 29 May 2026 07:21:02 GMT</pubDate>
        <item>
            <title><![CDATA[JSON files in TypeScript Azure Functions]]></title>
            <description><![CDATA[<p>In the process of moving my blog to <a href="https://azure.microsoft.com/en-us/products/app-service/static">Azure Static Web Apps</a> I had to troubleshoot a strange issue: according to logs <a href="https://learn.microsoft.com/en-us/azure/static-web-apps/add-api">the managed API Azure Functions</a> have been successfully deployed, but according to Azure Portal there weren&#39;t any running. And I couldn&#39;t find any errors logged anywhere.</p>
<p>Since I was working in Visual Studio Code, I added the function to my Azure Static Web App using the <strong>Azure Static Web Apps: Create HTTP Function...</strong> command provided <a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestaticwebapps">by the Azure Static Web Apps extension</a>.</p>
<p>The template created all the plumbing for me in the <code>api</code> directory of my repository, including everything that was needed for building and running the functions locally: the scripts in <code>package.json</code> file, as well as the tasks and launch targets for Visual Studio Code. After I also installed <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Chttp-trigger%2Ccontainer-apps&amp;pivots=programming-language-typescript#install-the-azure-functions-core-tools">Azure Functions Core Tools</a>, I could run and test the function locally before deploying it to Azure.</p>
<p>My code depended on some static data in a JSON file, which I imported into my source code file. While I did it using the <code>resolveJsonModule</code> compiler option, the Azure Function worked as expected.</p>
<p>But once I used the <code>allowArbitraryExtensions</code> compiler option instead, to allow for <a href="20260522-ImportingJsonWithCustomTypesInTypescript.html">custom type definition for my JSON file</a>, the Azure Function was simply gone: Any calls to it returned 404 and the <strong>APIs</strong> settings page of my Azure Static Web App didn&#39;t show any deployed functions. To make matters worse, I couldn&#39;t find any error logs anywhere: not in Azure Portal and not in GitHub Actions which I used for deployment. I couldn&#39;t even enable <strong>Application Insights</strong> in the Azure Static Web App settings, because I supposedly didn&#39;t have any functions:</p>
<p><img src="img/20060529-JsonAzureFunctionError.png" alt="No Application Insights for Azure Static Web Apps without Azure Functions"></p>
<p>Fortunately, I could reproduce the issue by trying to run the function locally. It failed to run with the following error:</p>
<blockquote>
<p>Worker was unable to load entry point <code>dist/src/functions/persons.js</code>: Cannot find module <code>./persons.json</code></p>
</blockquote>
<p>Further research revealed that the <code>build</code> script copied the JSON file to the output folder as long as it depended on the <code>resolveJsonModule</code> compiler option, but not anymore after I started relying on the <code>allowArbitraryExtensions</code> compiler option to use custom type definitions.</p>
<p>Two scripts from the generated <code>packages.json</code> file were relevant for my issue:</p>
<pre class="highlight"><code class="hljs json">{
  "<span class="hljs-attribute">scripts</span>": <span class="hljs-value">{
    "<span class="hljs-attribute">build</span>": <span class="hljs-value"><span class="hljs-string">"tsc"</span></span>,
    "<span class="hljs-attribute">watch</span>": <span class="hljs-value"><span class="hljs-string">"tsc -w"</span>
  </span>}
</span>}
</code></pre>
<p>The <code>build</code> script was used during deployment, and the <code>watch</code> script was used when running the function locally. Both simply invoked the TypeScript compiler to generate the JavaScript files and copy the results to the output folder. I guess, the compiler copies the JSON files which are resolved as JSON modules but no the ones which are considered having arbitrary extensions.</p>
<p>I wasn&#39;t really keen on introducing a bundler for the build process, so I decided to simply copy the JSON files across during the build process as <a href="https://janaagaard.com/blog/2019-06-12-part-2-switch-to-typescript">suggested by Jan Asgaard in his blog post</a>. I installed the <code>copyfiles</code> package:</p>
<pre class="highlight"><code class="hljs q">npm install --<span class="hljs-built_in">save</span>-<span class="hljs-built_in">dev</span> copyfiles
</code></pre>
<p>And used it to copy the JSON files from the relevant script files:</p>
<pre class="highlight"><code class="hljs json">{
  "<span class="hljs-attribute">scripts</span>": <span class="hljs-value">{
    "<span class="hljs-attribute">build</span>": <span class="hljs-value"><span class="hljs-string">"tsc &amp;&amp; copyfiles \"src/**/*.json\" dist"</span></span>,
    "<span class="hljs-attribute">watch</span>": <span class="hljs-value"><span class="hljs-string">"copyfiles \"src/**/*.json\" dist &amp;&amp; tsc -w"</span>
  </span>}
</span>}
</code></pre>
<p>In the <code>build</code> script, I could run it after the compiler was done, but in the <code>watch</code> script I had to do it before invoking the compiler because its process kept running to watch for file changes. My approach also meant that the JSON files were not going to be updated automatically in the output folder if I changed them while the the compiler file watch was active. That was good enough for me.</p>
<p>With this change, the function worked again after the next deploy to Azure Static Web Apps.</p>
<p>You can find a sample project in my <a href="https://github.com/DamirsCorner/20260529-ts-json-azure-function">GitHub repository</a>. Individual commits follow the steps described in this post:</p>
<ul>
<li>a working function using <code>resolveJsonModule</code>,</li>
<li>a broken function using <code>allowArbitraryExtensions</code>,</li>
<li>and a working function again with the modified <code>build</code> and <code>watch</code> scripts.</li>
</ul>
<p>The repository also includes a GitHub Actions workflow to deploy the sample site with the function to Azure Static Web Apps. Of course, you&#39;ll have to create your own Azure Static Web App in order to do that, and store <a href="https://learn.microsoft.com/en-us/azure/static-web-apps/deployment-token-management">the deployment token for it</a> in the <code>AZURE_STATIC_WEB_APPS_API_TOKEN</code> secret in GitHub Actions.</p>
<p>I find it interesting that the handling of JSON files by the TypeScript compiler depends on how you import them into the source code file. In a way, that is also <a href="https://www.typescriptlang.org/tsconfig/#allowArbitraryExtensions">documented</a>, although it might not be obvious until you are aware of that behavior:</p>
<blockquote>
<p>By default, this import will raise an error to let you know that TypeScript doesn’t understand this file type and your runtime might not support importing it. But if you’ve configured your runtime or bundler to handle it, you can suppress the error with the new <code>--allowArbitraryExtensions</code> compiler option.</p>
</blockquote>
<p>Although using a bundler would be a proper solution in such a case, I find it an overkill for my scenario. Simply copying the files to the output folder works well enough for now. I can always introduce a bundler in the future if I need to.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20260529-JsonFilesInTypescriptAzureFunctions.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20260529-JsonFilesInTypescriptAzureFunctions.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Importing JSON with custom types in TypeScript]]></title>
            <description><![CDATA[<p>When you want to embed some static data in your TypeScript application, it&#39;s very convenient to store it as a JSON file and import it directly in a source code file. The compiler even implies static typing for the data. If it doesn&#39;t describe your data good enough, there&#39;s a way to provide your own type definition.</p>
<p>To make imports work with JSON data, you need to <a href="https://www.typescriptlang.org/tsconfig/#resolveJsonModule">enable <code>resolveJsonModule</code></a> in your <code>tsconfig.json</code> file (it seems to be enabled by default when you have <a href="https://www.typescriptlang.org/docs/handbook/modules/reference.html#the-module-compiler-option"><code>module</code> compiler option</a> set to <code>node20</code> or <code>nodenext</code>, but it doesn&#39;t hurt to set it explicitly anyway):</p>
<pre class="highlight"><code class="hljs json">{
  "<span class="hljs-attribute">resolveJsonModule</span>": <span class="hljs-value"><span class="hljs-literal">true</span>
</span>}
</code></pre>
<p>Then you can import a JSON file just like a regular TypeScript file:</p>
<pre class="highlight"><code class="hljs typescript"><span class="hljs-keyword">import</span> lookups from <span class="hljs-string">"./lookups.json"</span>;
</code></pre>
<p>This initializes a <code>lookups</code> variable with the data from the JSON file and you can use it in your code like any other variable:</p>
<pre class="highlight"><code class="hljs typescript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> lookup of lookups) {
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>And it&#39;s not even typed as <code>any</code>. The compiler assigns it a type which matches the file contents.</p>
<p>In many cases this works good enough. But it made the types too specific for my use when I was dealing with a key value dictionary:</p>
<pre class="highlight"><code class="hljs json">[
  {
    "<span class="hljs-attribute">separator</span>": <span class="hljs-value"><span class="hljs-string">","</span></span>,
    "<span class="hljs-attribute">index</span>": <span class="hljs-value"><span class="hljs-number">2</span></span>,
    "<span class="hljs-attribute">lookups</span>": <span class="hljs-value">{
      "<span class="hljs-attribute">one</span>": <span class="hljs-value"><span class="hljs-string">"1"</span></span>,
      "<span class="hljs-attribute">two</span>": <span class="hljs-value"><span class="hljs-string">"2"</span></span>,
      "<span class="hljs-attribute">three</span>": <span class="hljs-value"><span class="hljs-string">"3"</span></span>,
      "<span class="hljs-attribute">four</span>": <span class="hljs-value"><span class="hljs-string">"4"</span></span>,
      "<span class="hljs-attribute">five</span>": <span class="hljs-value"><span class="hljs-string">"5"</span>
    </span>}
  </span>},
  {
    "<span class="hljs-attribute">separator</span>": <span class="hljs-value"><span class="hljs-string">";"</span></span>,
    "<span class="hljs-attribute">index</span>": <span class="hljs-value"><span class="hljs-number">1</span></span>,
    "<span class="hljs-attribute">lookups</span>": <span class="hljs-value">{
      "<span class="hljs-attribute">1</span>": <span class="hljs-value"><span class="hljs-string">"a"</span></span>,
      "<span class="hljs-attribute">2</span>": <span class="hljs-value"><span class="hljs-string">"b"</span></span>,
      "<span class="hljs-attribute">3</span>": <span class="hljs-value"><span class="hljs-string">"c"</span></span>,
      "<span class="hljs-attribute">4</span>": <span class="hljs-value"><span class="hljs-string">"d"</span></span>,
      "<span class="hljs-attribute">5</span>": <span class="hljs-value"><span class="hljs-string">"e"</span>
    </span>}
  </span>}
]
</code></pre>
<p>When I tried to access the <code>lookups</code> dictionary field by a string key, I got a compiler error:</p>
<p><img src="img/20060522-JsonImportTypeError.png" alt="Type error when accessing imported JSON data"></p>
<p>When I took a look at the type the compiler prepared for me, it quickly became obvious why it didn&#39;t work:</p>
<pre class="highlight"><code class="hljs typescript"><span class="hljs-keyword">const</span> lookup:
  | {
      separator: <span class="hljs-built_in">string</span>;
      index: <span class="hljs-built_in">number</span>;
      lookups: {
        one: <span class="hljs-built_in">string</span>;
        two: <span class="hljs-built_in">string</span>;
        three: <span class="hljs-built_in">string</span>;
        four: <span class="hljs-built_in">string</span>;
        five: <span class="hljs-built_in">string</span>;
        <span class="hljs-string">"1"</span>?: never;
        <span class="hljs-string">"2"</span>?: never;
        <span class="hljs-string">"3"</span>?: never;
        <span class="hljs-string">"4"</span>?: never;
        <span class="hljs-string">"5"</span>?: never;
      };
    }
  | {
      separator: <span class="hljs-built_in">string</span>;
      index: <span class="hljs-built_in">number</span>;
      lookups: {
        <span class="hljs-string">"1"</span>: <span class="hljs-built_in">string</span>;
        <span class="hljs-string">"2"</span>: <span class="hljs-built_in">string</span>;
        <span class="hljs-string">"3"</span>: <span class="hljs-built_in">string</span>;
        <span class="hljs-string">"4"</span>: <span class="hljs-built_in">string</span>;
        <span class="hljs-string">"5"</span>: <span class="hljs-built_in">string</span>;
        one?: never;
        two?: never;
        three?: never;
        four?: never;
        five?: never;
      };
    };
</code></pre>
<p>The type was much too specific for my needs. I wanted the <code>lookups</code> field to be treated as a <code>Record&lt;string, string&gt;</code>.</p>
<p>Fortunately, there&#39;s a way to specify your own type definitions for data from an imported JSON file. To make it work, you need to set <a href="https://www.typescriptlang.org/tsconfig/#allowArbitraryExtensions">the <code>allowArbitraryExtensions</code> compiler option</a> instead of <code>resolveJsonModule</code>:</p>
<pre class="highlight"><code class="hljs json">{
  "<span class="hljs-attribute">allowArbitraryExtensions</span>": <span class="hljs-value"><span class="hljs-literal">true</span>
</span>}
</code></pre>
<p>Then you can create a type definition for your JSON file with the same base filename, but with a <code>.d.json.ts</code> extension instead of <code>.json</code>, i.e., for my <code>lookups.json</code> file I had to create a type definition file <code>lookups.d.json.ts</code> with the following contents:</p>
<pre class="highlight"><code class="hljs typescript"><span class="hljs-keyword">declare</span> <span class="hljs-keyword">const</span> lookups: {
  separator: <span class="hljs-built_in">string</span>;
  index: <span class="hljs-built_in">number</span>;
  lookups: Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt;;
}[];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> lookups;
</code></pre>
<p>With these two changes, the compiler imported the JSON file with my types instead of its own implied ones. And with that, the compiler error for my dictionary lookup by key was resolved.</p>
<p>You can find a sample project in my <a href="https://github.com/DamirsCorner/20260522-ts-json-custom-types">GitHub repository</a>. The last commit showcases the approach with the <code>allowArbitraryExtensions</code> compiler option and the one before that uses the <code>resolveJsonModule</code> compiler option.</p>
<p>Although <code>resolveJsonModule</code> works great for importing JSON files in most cases, it&#39;s good to know that there is an option to override default implied type information when you need to by using <code>allowArbitraryExtensions</code>. Just keep in mind that it&#39;s your responsibility to make sure that the data in the JSON file matches your type definition. Otherwise you can expect run-time errors in your application when accessing that data.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20260522-ImportingJsonWithCustomTypesInTypescript.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20260522-ImportingJsonWithCustomTypesInTypescript.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[A reason VS debugger might not hit breakpoints]]></title>
            <description><![CDATA[<p>There&#39;s an open source application plugin written in .NET I occasionally contribute to. As a regular part of my workflow, I sometimes have to attach the debugger to the application with the plugin loaded to troubleshoot unexpected behavior. When I did that the last time, the breakpoints in Visual Studio for some reason didn&#39;t work.</p>
<p>The breakpoint icons included a warning icon and when I hovered over them, the following message appeared in the tooltip:</p>
<blockquote>
<p>The breakpoint will not currently be hit. No symbols have been loaded for this document.</p>
</blockquote>
<p><img src="img/20060515-PathMapDisabledBreakpoint.png" alt="Disabled breakpoint with a warning in Visual Studio"></p>
<p>Usually this happens when no symbol (<code>.pdb</code>) file is available next to the plugin assembly (<code>.dll</code>) file or when it&#39;s out-of-date and it doesn&#39;t match the assembly. I followed my standard procedure for resolving such an issue:</p>
<ul>
<li>close the application,</li>
<li>rebuild the plugin project,</li>
<li>copy the new assembly and symbol file to application plugin folder,</li>
<li>restart the application,</li>
<li>and attach the debugger to its process again.</li>
</ul>
<p>However, breakpoints still didn&#39;t work. They worked flawlessly the previous time I worked on the plugin, and I couldn&#39;t recall making any changes to my Visual Studio setup that would cause the issue.</p>
<p>I checked out the version of the plugin I worked on that time and repeated the whole rebuild and redeploy process. The breakpoints worked as expected. I tried it again with the latest version and the breakpoints stopped working.</p>
<p>The only explanation I could think of was that between the two versions of the plugin something has changed that broke the breakpoints (at least for me). Fortunately, there weren&#39;t too many changes and I quickly noticed one that looked like a potential culprit. The following line was added to the project (<code>.csproj</code>) file with one of the commits:</p>
<pre class="highlight"><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-title">PathMap</span>&gt;</span>$(MSBuildProjectDirectory)=/_/<span class="hljs-tag">&lt;/<span class="hljs-title">PathMap</span>&gt;</span>
</code></pre>
<p>I wasn&#39;t familiar with that MSBuild property, so I looked it up <a href="https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties">in the documentation</a>:</p>
<blockquote>
<p>Specifies how to map physical paths to source path names output by the compiler. This property is equivalent to the <code>/pathmap</code> switch of the compilers.</p>
</blockquote>
<p>This confirmed my suspicion: the property interferes with the source file paths in the symbol files. I tried removing the offending line: after a rebuild and redeploy, breakpoints worked again.</p>
<p>To see how exactly the property affects the symbol files, I opened both of them in a text editor and searched for a random <code>.cs</code> file reference in it:</p>
<ul>
<li>The symbol file built without the <code>PathMap</code> property contained the absolute path to the source file on my disk: <code>C:\Users\Damir\Git\PluginSln\PluginProj\SourceDir\SourceFile.cs</code>.</li>
<li>The symbol file built with the <code>PathMap</code> property had the absolute path to the solution directory replaced with <code>/_/</code>: <code>/_/PluginProj/SourceDir/SourceFile.cs</code>. Because of this change, the Visual Studio debugger failed to map the source files.</li>
</ul>
<p>I&#39;m not sure what the benefit of such <code>PathMap</code> setting is except if you plan to distribute the symbol files and don&#39;t want them to give away the absolute paths from the machine they were built on.</p>
<p>Since it&#39;s not my plugin project, I didn&#39;t want to commit my reversed change. Instead, I created a git stash with the <code>PathMap</code> change removed which I can apply whenever I work on the plugin and need to debug it. And I make sure not to commit the change when I contribute to the project.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20260515-AReasonVsDebuggerMightNotHitBreakpoints.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20260515-AReasonVsDebuggerMightNotHitBreakpoints.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[My Bitwarden backup script]]></title>
            <description><![CDATA[<p>Inspired by <a href="https://community.bitwarden.com/t/how-to-a-users-guide-to-backing-up-your-bitwarden-vault/44083">a great post in Bitwarden Community Forums</a> and <a href="https://github.com/dh024/Bitwarden_Export">the author&#39;s bash script</a> I decided to create my own PowerShell script for backing up the contents of my Bitwarden vault to <a href="20250718-BackingUpMyWindowsWorkstationUsingRestic.html">my existing restic backup</a>.</p>
<p>The backup script relies on the <a href="https://bitwarden.com/help/cli/">Bitwarden CLI</a>. I <a href="https://bitwarden.com/help/cli/#tab-chocolatey-bI3gMs3A3z4pl0fwvRie9">installed it using Chocolatey</a>:</p>
<pre class="highlight"><code class="hljs powershell">choco install bitwarden-cli
</code></pre>
<p>Unlike the original script, I wanted to run my on a schedule and non-interactively. I stored my credentials in a separate script file that&#39;s only accessible with elevated privileges as described in <a href="20260320-SecureScriptsWithSecretsInWindows.html">a previous blog post</a>:</p>
<pre class="highlight"><code class="hljs powershell"><span class="hljs-variable">$env:BW_CLIENTID</span> = <span class="hljs-string">"client_id"</span>
<span class="hljs-variable">$env:BW_CLIENTSECRET</span> = <span class="hljs-string">"client_secret"</span>
<span class="hljs-variable">$env:BW_PASSWORD</span> = <span class="hljs-string">"password"</span>
</code></pre>
<p>The <code>BW_PASSWORD</code> environment variable contains my Bitwarden password. The values for <code>BW_CLIENTID</code> and <code>BW_CLIENTSECRET</code> can be accessed from <a href="https://vault.bitwarden.com/#/settings/security/security-keys">the <strong>Security Settings</strong> page of Bitwarden web interface</a>:</p>
<p><img src="img/20060327-BitwardenBackupApiKey.png" alt="Bitwarden API key in web interface"></p>
<p>My backup script starts by invoking the script with my credentials, which stores them in <a href="https://bitwarden.com/help/cli/#using-an-api-key">the documented environment variables</a>. I then log in using the API key and <a href="https://bitwarden.com/help/cli/#unlock-options">unlock the vault</a> using the password from the specified environment variable. I store the returned <a href="https://bitwarden.com/help/cli/#using-a-session-key">session key</a> into another environment variable:</p>
<pre class="highlight"><code class="hljs powershell">bw login --apikey
<span class="hljs-variable">$sessionKey</span> = bw unlock --passwordenv BW_PASSWORD --raw
<span class="hljs-variable">$env:BW_SESSION</span> = <span class="hljs-variable">$sessionKey</span>
</code></pre>
<p>This is enough to make vault contents accessible through CLI commands.</p>
<p>I <a href="https://bitwarden.com/help/cli/#export">export the vault</a> into an unencrypted JSON file since it&#39;s encrypted by restic anyway:</p>
<pre class="highlight"><code class="hljs powershell"><span class="hljs-variable">$backupFile</span> = <span class="hljs-built_in">Join-Path</span> <span class="hljs-variable">$backupDir</span> <span class="hljs-string">"export.json"</span>
bw export --format json --output <span class="hljs-variable">$backupFile</span>
</code></pre>
<p>The JSON file doesn&#39;t include the attachments. Since I&#39;m using them, I have to export those manually:</p>
<pre class="highlight"><code class="hljs powershell">bw list items |
  ConvertFrom-Json |
  <span class="hljs-built_in">Where-Object</span> { <span class="hljs-variable">$_</span>.attachments.Count <span class="hljs-operator">-gt</span> <span class="hljs-number">0</span> } |
  <span class="hljs-built_in">ForEach-Object</span> {
    <span class="hljs-variable">$attachmentsDir</span> = <span class="hljs-built_in">Join-Path</span> <span class="hljs-variable">$backupDir</span> <span class="hljs-variable">$_</span>.id
    <span class="hljs-keyword">if</span> (<span class="hljs-operator">-not</span> (<span class="hljs-built_in">Test-Path</span> <span class="hljs-variable">$attachmentsDir</span>)) {
      <span class="hljs-built_in">New-Item</span> -ItemType Directory -Path <span class="hljs-variable">$attachmentsDir</span> | <span class="hljs-built_in">Out-Null</span>
    }
    <span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$attachment</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$_</span>.attachments) {
      <span class="hljs-variable">$attachmentFullPath</span> = <span class="hljs-built_in">Join-Path</span> <span class="hljs-variable">$attachmentsDir</span> <span class="hljs-variable">$attachment</span>.fileName
      bw get attachment <span class="hljs-variable">$attachment</span>.fileName --itemid <span class="hljs-variable">$_</span>.id --output <span class="hljs-variable">$attachmentFullPath</span>
    }
  }
</code></pre>
<p>To find the attachments, I <a href="https://bitwarden.com/help/cli/#list">list the items</a> from the vault. I let PowerShell <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertfrom-json">parse the returned JSON</a> and then iterate through the items which have attachments. To be able to map those attachments back to the items, I store them in a separate directory for each item, using the item <code>id</code> as the directory name. After creating the directory, I iterate through the item attachments and <a href="https://bitwarden.com/help/cli/#get">download them</a> keeping the attachment filename.</p>
<p>At the end of the script, I <a href="https://bitwarden.com/help/cli/#using-a-session-key">log out</a> from the sessions to prevent any further access to the vault:</p>
<pre class="highlight"><code class="hljs powershell">bw logout
</code></pre>
<p>The main restic backup script includes the <code>$backupDir</code> in the backup and deletes it afterwards to prevent further access to exported vault contents.</p>
<p>The backup gives me the peace of mind of having all the credentials securely stored and still available in the unlikely case that I couldn&#39;t access my Bitwarden vault anymore. Thanks to <a href="20250711-BackingUpLinuxHomeServerUsingRestic.html">my restic backup policy</a>, it also allows me to access any accidentally deleted items beyond the built-in <a href="https://bitwarden.com/help/managing-items/#delete">vault trash</a> functionality.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20260327-MyBitwardenBackupScript.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20260327-MyBitwardenBackupScript.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Secure scripts with secrets in Windows]]></title>
            <description><![CDATA[<p>My <a href="20250718-BackingUpMyWindowsWorkstationUsingRestic.html">restic backup script on Windows</a> relied on having <a href="https://restic.readthedocs.io/en/stable/040_backup.html#environment-variables">the restic password stored in an environment variable</a>, making it and the restic command accessible to any process running with my credentials. Before including more sensitive information in my backup, I wanted to make it more secure. I ended up moving the credentials to a file that&#39;s only accessible with elevated privileges.</p>
<p>To impact the existing script as little as possible and keep the restic calls convenient without having to specify the password for every call, the above-mentioned newly created file is just another PowerShell script which sets the environment variable with the restic password:</p>
<pre class="highlight"><code class="hljs powershell"><span class="hljs-variable">$env:RESTIC_PASSWORD</span> = <span class="hljs-string">'my_restic_password'</span>
</code></pre>
<p>To make the file only accessible with elevated privileges, I had to restrict its access to the <strong>Administrators</strong> (and <strong>SYSTEM</strong>) group. This can be achieved by following these steps:</p>
<ul>
<li>Open file <strong>Properties</strong>, navigate to <strong>Security</strong> tab and click <strong>Advanced</strong>.</li>
<li>Click <strong>Disable inheritance</strong> and choose <strong>Convert inherited permissions into explicit permissions on this object</strong> in the next dialog.</li>
<li><strong>Remove</strong> current user from permission entries.</li>
<li><strong>Change</strong> owner to <code>Administrators</code> group.</li>
</ul>
<p><img src="img/20260320-SecureFilePermissions.png" alt="File permissions for the secured file"></p>
<p>After applying this configuration, you&#39;ll have to run your text editor as administrator if you want to make any further changes to the script.</p>
<p>To make the environment variable with the password available when running the backup script, I added the following call at the very beginning of it to invoke my new secure script containing the credentials:</p>
<pre class="highlight"><code class="hljs powershell">. ./MySetEnvScript.ps1
</code></pre>
<p>Of course, this call will only succeed when the calling script is running with elevated privileges. Since my backup script is invoked from the <strong>Task Scheduler</strong>, I had to check <strong>Run with highest privileges</strong> on the <strong>General</strong> tab of the <strong>Properties</strong> window for the scheduled task to elevate its run-time privileges:</p>
<p><img src="img/20260320-SecureScheduledTaskProperties.png" alt="Scheduled task with elevated privileges"></p>
<p>Doing this had the unfortunate side effect of making the mapped network drive with my restic repository inaccessible. To resolve the issue I had to map the drive in my new secure script which meant adding another password to it:</p>
<pre class="highlight"><code class="hljs powershell">net use R: <span class="hljs-string">"\\server\share"</span> <span class="hljs-string">"mySharePassword"</span> /user:myShareUser

<span class="hljs-variable">$env:RESTIC_PASSWORD</span> = <span class="hljs-string">'my_restic_password'</span>
<span class="hljs-variable">$env:RESTIC_REPOSITORY</span> = <span class="hljs-string">"R:\"</span>
</code></pre>
<p>With this change, the backup script worked again.</p>
<p>Of course, I couldn&#39;t make any restic calls from the command line anymore without entering the password every time. This was the end goal of the whole endeavor after all. Since this makes using restic really inconvenient, I now usually do that from an elevated terminal window. From it, I first invoke my secure script with the password the same way as from the backup script. From that point on, I can again use restic without having to manually enter the password. And once I&#39;m done, I close the terminal window to avoid accidentally running any other commands in it.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20260320-SecureScriptsWithSecretsInWindows.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20260320-SecureScriptsWithSecretsInWindows.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Logging into Teams without a license]]></title>
            <description><![CDATA[<p>In the company I work for, we&#39;re not using Teams internally and our users don&#39;t have a license for it. But some of our customers do use Teams and pay for it. They also add users from our Entra ID which gives them access to Teams inside their organization. This worked great until it suddenly didn&#39;t anymore.</p>
<p>When an existing user tried to login into Teams on a new device, they couldn&#39;t. However, Teams still worked fine on the old device where they logged in at an earlier point in time. Further investigation showed that the issue wasn&#39;t user-specific, the same happened to all users. This meant that the problem was organization-wide.</p>
<p>The issue was even more difficult to troubleshoot because there was no useful error message accompanying the failed login in Teams application. Fortunately it could easily be reproduced when trying to log into <a href="https://teams.microsoft.com/v2/">Teams</a> from a private browser window. After logging in with a company Microsoft account, a page with a <strong>Sign in</strong> button showed up. Clicking the button didn&#39;t seem to do anything.</p>
<p><img src="img/20260227-TeamsLoginPage.png" alt="Teams login page in private browser window"></p>
<p>However, the browser developer tools provided more details. Each click resulted in a failed call to <code>https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token</code>. And the response body contained the following error message:</p>
<blockquote>
<p>AADSTS500014: The service principal for resource &#39;<a href="https://api.spaces.skype.com">https://api.spaces.skype.com</a>&#39; is disabled. This indicate that a subscription within the tenant has lapsed, or that the administrator for this tenant has disabled the application, preventing tokens from being issued for it.</p>
</blockquote>
<p>This helped us discover that <a href="https://learn.microsoft.com/en-us/answers/questions/980425/the-service-principal-for-resource-https-api-space">we weren&#39;t the only ones with this issue</a>. And it identified the root cause of the error: the Microsoft Teams application has become disabled. Could we somehow reenable it?</p>
<p>It was time to take a closer look in the <a href="https://entra.microsoft.com">Microsoft Entra admin center</a>. The Microsoft Teams app should be listed on the <strong>Enterprise apps</strong> page. It didn&#39;t look like it at first sight:</p>
<p><img src="img/20260227-TeamsEnterpriseApplications.png" alt="Default enterprise applications filter in Microsoft Entra"></p>
<p>Removing the <strong>Application type == Enterprise Applications</strong> filter helped. Now, there were <em>a lot</em> of Microsoft Teams applications listed:</p>
<p><img src="img/20260227-TeamsMicrosoftTeamsApplications.png" alt="Microsoft Teams enterprise applications in Microsoft Entra"></p>
<p><strong>Microsoft Teams Services</strong> was the application I was looking for. On its <strong>Properties</strong> page I could see that it was <strong>Deactivated</strong>:</p>
<p><img src="img/20260227-TeamsMicrosoftTeamsProperties.png" alt="Microsoft Teams properties in Microsoft Entra"></p>
<p>Toggling the <strong>Enabled for users to sign-in</strong> switch to <strong>Yes</strong> changed its <strong>Activation status</strong> to <strong>Activated</strong>. It was time try logging into Teams from a private browser window again. Unfortunately it still didn&#39;t work. But there was now a different error in the browser development tools:</p>
<blockquote>
<p>AADSTS500014: The service principal for resource &#39;00000003-0000-0ff1-ce00-000000000000&#39; is disabled. This indicate that a subscription within the tenant has lapsed, or that the administrator for this tenant has disabled the application, preventing tokens from being issued for it.</p>
</blockquote>
<p>Finding the right application in the Microsoft Entra Enterprise applications list was even easier this time. I could search by the <strong>Application ID</strong> value from the error message. It was the <strong>Office 365 SharePoint Service</strong> application and it was also <strong>Deactivated</strong>. I reactivated it the same way as <strong>Microsoft Teams</strong>. And I got yet another error when I tried to log in:</p>
<blockquote>
<p>AADSTS7000112: Application &#39;a164aee5-7d0a-46bb-9404-37421d58bdf7&#39;(Microsoft Teams AuthSvc) is disabled.</p>
</blockquote>
<p>This time both the application <strong>Name</strong> and <strong>Application ID</strong> were listed in the error message. As if they were trying to make it easier for me. I reactivated <strong>Microsoft Teams AuthSvc</strong> just like the other two applications.</p>
<p>This finally fixed the issue for good. The login in the private browser window succeeded. I got a list of organizations I was added to. After choosing one of them I was logged into their Teams tenant. It worked from the Teams application as well. Let&#39;s just hope it stays that way.</p>
<p>And if not, I know what to check first. In the <strong>Microsoft Entra admin center</strong> the following <strong>Enterprise applications</strong> must be <strong>Activated</strong> (i.e., the <strong>Enabled for users to sign-in</strong> switch on the application <strong>Properties</strong> page must be set to <strong>Yes</strong>):</p>
<ul>
<li>Microsoft Teams Services, Application ID: cc15fd57-2c6c-4117-a88c-83b1d56b4bbe</li>
<li>Office 365 SharePoint Online, Application ID: 00000003-0000-0ff1-ce00-000000000000</li>
<li>Microsoft Teams AuthSvc, Application ID: a164aee5-7d0a-46bb-9404-37421d58bdf7</li>
</ul>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20260227-LoggingIntoTeamsWithoutALicense.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20260227-LoggingIntoTeamsWithoutALicense.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Disable Quarkus endpoints with a request filter]]></title>
            <description><![CDATA[<p>I needed a simple way to disable all endpoints in a Quarkus application based on a configuration property. A <a href="https://quarkus.io/guides/rest#request-or-response-filters">request filter</a> was the right tool for the job, but some care had to be taken to not also disable <a href="https://quarkus.io/guides/dev-ui">the Dev UI</a>.</p>
<p>A server request filter is a method annotated with <code>@ServerRequestFilter</code>. To access a configuration property, inject it into is parent class:</p>
<pre class="highlight"><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Filters</span> </span>{
    <span class="hljs-annotation">@ConfigProperty</span>(name=<span class="hljs-string">"features.endpoints-enabled"</span>)
    <span class="hljs-keyword">boolean</span> featureEndpointsEnabled;

    <span class="hljs-annotation">@ServerRequestFilter</span>
    <span class="hljs-keyword">public</span> RestResponse&lt;?&gt; featureFlagFilter(ContainerRequestContext requestContext) {
        <span class="hljs-keyword">if</span> (!featureEndpointsEnabled &amp;&amp;
                !requestContext.getUriInfo().getPath().startsWith(<span class="hljs-string">"/q/"</span>)) {
            <span class="hljs-keyword">return</span> RestResponse.ResponseBuilder.notFound().build();
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }
}
</code></pre>
<p>When the method returns <code>null</code>, the processing will proceed, making the endpoint accessible. When the method returns a <code>RestResponse</code>, the processing stops and that response will be returned to the caller.</p>
<p>If you still want the Dev UI to work when you disable the endpoints, you need to check the requested path, and not block requests to paths which start with <code>/q/</code>.</p>
<p>To test the filter, you would need to change the value of a configuration property for the particular test. One way to do this is by using <a href="https://quarkus.io/blog/overriding-configuration-from-test-code/#approach-1-quarkus-test-profiles">Quarkus test profiles</a>. The test method must be placed in a separate test class which also acts as a test profile:</p>
<pre class="highlight"><code class="hljs java"><span class="hljs-annotation">@QuarkusTest</span>
<span class="hljs-annotation">@TestProfile</span>(DisabledExampleResourceTest.class)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DisabledExampleResourceTest</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">QuarkusTestProfile</span> </span>{

    <span class="hljs-annotation">@Override</span>
    <span class="hljs-keyword">public</span> Map&lt;String, String&gt; getConfigOverrides() {
        <span class="hljs-keyword">return</span> Map.of(
                <span class="hljs-string">"features.endpoints-enabled"</span>, <span class="hljs-string">"false"</span>
        );
    }

    <span class="hljs-annotation">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">testHelloEndpointDisabled</span><span class="hljs-params">()</span> </span>{
        given()
                .when().get(<span class="hljs-string">"/hello"</span>)
                .then()
                .statusCode(<span class="hljs-number">404</span>)
                .body(is(<span class="hljs-string">""</span>));
    }
}
</code></pre>
<p>The <code>getConfigOverrides</code> method returns the key-value pairs of configuration properties to override.</p>
<p>You can find a sample project in my <a href="https://github.com/DamirsCorner/20260102-quarkus-request-filter">GitHub repository</a>. It adds the filter to a new Quarkus project with a sample endpoint, and a test that validates its behavior.</p>
<p>Although request filters aren&#39;t all that flexible, they are a great fit for a functionality that affects all or many endpoints in the application.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20260102-DisableQuarkusEndpointsWithARequestFilter.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20260102-DisableQuarkusEndpointsWithARequestFilter.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Switching between JDKs in Windows]]></title>
            <description><![CDATA[<p>IntelliJ IDEA does a great at <a href="https://www.jetbrains.com/help/idea/sdk.html">managing multiple different JDK versions</a> and using the right one for each project. However, a different solution is needed for the command line. I tried <a href="https://vfox.dev">vfox</a>, but I couldn&#39;t get <a href="https://vfox.dev/usage/core-commands.html#use">version switching</a> to work. I could still use it as a glorified JDK <a href="https://vfox.dev/usage/core-commands.html#install">installer</a>, but IntelliJ IDEA already fulfills this role. I now use PowerShell to switch between JDKs installed from IntelliJ IDEA in the command line as well.</p>
<p>To start with a clean slate, I first removed all the JDKs except the ones installed from IntelliJ IDEA. This included the ones listed in Windows Installed apps and those installed through vfox while I was testing it. I also made sure to delete the <code>JAVA_HOME</code> environment variable (at system and user level) and removed any Java related entries from the <code>PATH</code> environment variable (again at system and user level).</p>
<p>I then checked the JDKs I had installed in IntelliJ IDEA. To do that, I opened a Java project in IntelliJ IDEA, opened <strong>File</strong> &gt; <strong>Project Structure...</strong> from the main menu and navigated to <strong>Platform Settings</strong> &gt; <strong>SDKs</strong>. I selected each JDK in the list and copied the JDK home paths for future reference:</p>
<ul>
<li><code>openjdk-25</code>: <code>C:\Users\damir\.jdks\openjdk-25.0.1</code></li>
<li><code>openjdk-23</code>: <code>C:\Users\damir\.jdks\openjdk-23.0.1</code></li>
</ul>
<p><img src="img/20251226-JdkIdea.png" alt="JDK management in IntelliJ IDEA"></p>
<p>I decided to use JDK 25 by default, so I made the following changes to my user environment variables:</p>
<ul>
<li>I set <code>JAVA_HOME</code> to the JDK path: <code>C:\Users\damir\.jdks\openjdk-25.0.1</code>.</li>
<li>I added the <code>bin</code> subfolder inside it to <code>PATH</code>: <code>C:\Users\damir\.jdks\openjdk-25.0.1\bin</code>.</li>
</ul>
<p>This was enough to get Java 25 working in a newly opened terminal:</p>
<pre class="highlight"><code class="hljs no-highlight">➜ java -version
openjdk version &quot;25.0.1&quot; 2025-10-21
OpenJDK Runtime Environment (build 25.0.1+8-27)
OpenJDK 64-Bit Server VM (build 25.0.1+8-27, mixed mode, sharing)
</code></pre>
<p>For switching to a different JDK version, I first created a generic PowerShell script that sets the <code>JAVA_HOME</code> and <code>PATH</code> variables correctly for a JDK in a given folder:</p>
<pre class="highlight"><code class="hljs powershell"><span class="hljs-keyword">param</span>(
  [Parameter(Mandatory)]
  [string]<span class="hljs-variable">$Path</span>
)

<span class="hljs-variable">$env:JAVA_HOME</span> = <span class="hljs-variable">$Path</span>
<span class="hljs-variable">$env:Path</span> = <span class="hljs-variable">$env:JAVA_HOME</span> + <span class="hljs-string">"\bin;"</span> + <span class="hljs-variable">$env:Path</span>;
</code></pre>
<p>Two details worth mentioning:</p>
<ul>
<li>The environment variables are only changed for the given session. This allows me to use a different JDK version in each session.</li>
<li>Having the <code>bin</code> subfolder added to the start of the <code>PATH</code> causes the files from this folder to be preferred over the ones from the default JDK, which is listed later in the path.</li>
</ul>
<p>For convenience, I added the following two functions to <a href="https://learn.microsoft.com/en-us/powershell/scripting/learn/shell/creating-profiles#adding-customizations-to-your-profile">my <code>$PROFILE</code> file</a>:</p>
<pre class="highlight"><code class="hljs powershell"><span class="hljs-keyword">Function</span> Set-Jdk25
{
    Set-Jdk <span class="hljs-string">"C:\Users\damir\.jdks\openjdk-25.0.1"</span>
}

<span class="hljs-keyword">Function</span> Set-Jdk23
{
    Set-Jdk <span class="hljs-string">"C:\Users\damir\.jdks\openjdk-23.0.1"</span>
}
</code></pre>
<p>Now I can use them to switch to a different JDK in the current session:</p>
<pre class="highlight"><code class="hljs no-highlight">➜ Set-Jdk23
➜ java -version
openjdk version &quot;23.0.1&quot; 2024-10-15
OpenJDK Runtime Environment (build 23.0.1+11-39)
OpenJDK 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)
</code></pre>
<p>When I change the JDKs installed through IntelliJ IDEA, I only need to update the functions in my <code>$PROFILE</code> to match the set of installed JDKs and their paths. If I want to change the default JDK as well, I also need to update the <code>JAVA_HOME</code> and <code>PATH</code> environments accordingly.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20251226-SwitchingBetweenJdksInWindows.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20251226-SwitchingBetweenJdksInWindows.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 26 Dec 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Git repository on a network share]]></title>
            <description><![CDATA[<p>I recently played around with having some version-controlled files on a remote Linux server with SSH access. I still wanted to edit them locally in Visual Studio Code, so the <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh">Remote - SSH extension</a> seemed a perfect fit. Unfortunately, connecting to the server from Visual Studio Code failed with the following error:</p>
<blockquote>
<p>The remote host may not meet VS Code Server&#39;s prerequisites for glibc and libstdc++ (The remote host does not meet the prerequisites for running VS Code Server)</p>
</blockquote>
<p>The server did not meet <a href="https://code.visualstudio.com/docs/remote/faq#_can-i-run-vs-code-server-on-older-linux-distributions">the prerequisites</a>, so I had to start looking for other solutions.</p>
<p>I decided to share the directory with the files I wanted to edit on the server so that I could access them from my computer. Using this approach, I managed to initialize a Git repository, but as soon as I wanted to perform any Git operation inside it, it failed with the following error:</p>
<pre class="highlight"><code class="hljs no-highlight">fatal: detected dubious ownership in repository at &#39;//my-server/my-share/my-dir&#39;
&#39;//my-server/my-share/my-dir&#39; is owned by:
    (inconvertible) (S-1-5-21-577097838-3836064388-3576385918-3054)
but the current user is:
    MyWinPC/damir (S-1-5-21-804102101-2538954194-3365188178-1001)
To add an exception for this directory, call:

    git config --global --add safe.directory &#39;%(prefix)///my-server/my-share/my-dir&#39;
</code></pre>
<p>It was because of a security measure, <a href="https://github.blog/open-source/git/highlights-from-git-2-36/#stricter-repository-ownership-checks">introduced in Git 2.35.2</a>. Changing the ownership of the files on the share wasn&#39;t an option, so I executed the command suggested in the error message to bypass the check for that specific directory:</p>
<pre class="highlight"><code class="hljs bash">git config --global --add safe.directory <span class="hljs-string">'%(prefix)///my-server/my-share/my-dir'</span>
</code></pre>
<p>This has indeed resolved the issue, so that I could perform all Git operations as usual, both form command line and from GUI clients.</p>
<p>Before proceeding, I wanted to make sure that all the files would have Linux line endings although I would be editing them from Windows. In Visual Studio Code you can use the <strong>Change End of Line Sequence</strong> command to select the line endings for each file:</p>
<p><img src="img/20251219-GitShareEolFile.png" alt="Configuring line endings for a file"></p>
<p>This works great for existing files, but there&#39;s always a risk of forgetting to switch the line endings when creating a new file. To avoid this, it&#39;s best to set the default value in workspace settings:</p>
<p><img src="img/20251219-GitShareEolWorkspace.png" alt="Configuring line endings for a workspace">
This setting will be persisted in <code>.vscode/settings.json</code> file, which can also be added to the git repository:</p>
<pre class="highlight"><code class="hljs json">{
  "<span class="hljs-attribute">files.eol</span>": <span class="hljs-value"><span class="hljs-string">"\n"</span>
</span>}
</code></pre>
<p>Git has <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_formatting_and_whitespace">its own approach to handling end of line differences</a> between Windows and Linux in files committed to a Git repository. If you&#39;re working in Windows, you most likely have it enabled globally with the <code>core.autocrlf</code> setting set to <code>true</code>. In this particular scenario, this setting would interfere with using Linux line endings in Windows. To prevent that, the setting can be turned off locally for this repository by running the following command from any directory inside it:</p>
<pre class="highlight"><code class="hljs bash">git config core.autocrlf <span class="hljs-literal">false</span>
</code></pre>
<p>With all this configured, I could edit the files on the remote Linux server from Visual Studio Code in Windows and have the files versioned in a Git repository, although Visual Studio Code couldn&#39;t connect remotely to the server using SSH. The experience was still inferior, but it worked well enough for my particular use case.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20251219-GitRepositoryOnANetworkShare.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20251219-GitRepositoryOnANetworkShare.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Per-folder git configuration]]></title>
            <description><![CDATA[<p>My work consists of writing code for multiple clients and for some of them I need to use a different email in my git commits. I recently learned that there is a better approach for handling this than configuring email per repository.</p>
<p>During <a href="https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup">initial git setup</a> you need to configure your name and email address to be used in git commits. Many graphical git clients provide user interface to do it, but you can also do it from command line:</p>
<pre class="highlight"><code class="hljs bash">git config --global user.name <span class="hljs-string">"Me"</span>
git config --global user.email <span class="hljs-string">"email@primary.domain"</span>
</code></pre>
<p>This configuration is saved to <code>~/.gitconfig</code> in your home folder:</p>
<pre class="highlight"><code class="hljs no-highlight">[user]
    name = Me
    email = email@primary.domain
</code></pre>
<p>By default, these settings are going to be used for all repositories you work with on your machine. You can however override any git configuration value for a specific repository. This includes your name and email. Some graphical git clients support this as well, but you can also do it by running the following commands from a folder inside that repository:</p>
<pre class="highlight"><code class="hljs bash">git config user.name <span class="hljs-string">"Me"</span>
git config user.email <span class="hljs-string">"email@secondary.domain"</span>
</code></pre>
<p>This configuration is saved to <code>.git/config</code> located at the repository root folder:</p>
<pre class="highlight"><code class="hljs no-highlight">[user]
    name = Me
    email = email@secondary.domain
</code></pre>
<p>When alternative configuration is set for a repository like this, the name and email from it are going to be used for all commits to this repository.</p>
<p>Although this configuration process is simple enough, it doesn&#39;t scale well if your client uses microservices. It&#39;s too easy to forget changing the configuration for every single microservice repository you clone to your machine and start contributing to it. And in that case the wrong email from your default global configuration is going to be used.</p>
<p>Fortunately, you can use <a href="https://git-scm.com/docs/git-config#_conditional_includes">conditional includes</a> to set the same configuration for all git repositories inside a specific parent folder. On my work machine, I already have all repositories for a certain client inside the same folder, so this matches well with my existing organization.</p>
<p>To use conditional includes, you first need to create a file with git configuration settings in standard format so that you can include it. I put it in the folder containing all the repositories from a specific client, but you could put it anywhere you like:</p>
<pre class="highlight"><code class="hljs no-highlight">[user]
    name = Me
    email = email@secondary.domain
</code></pre>
<p>Then, you need to open your global git configuration file in <code>~/.gitconfig</code> and conditionally include the configuration file you just created:</p>
<pre class="highlight"><code class="hljs no-highlight">[includeIf &quot;gitdir:~/Git/MyClient/&quot;]
    path = ~/Git/MyClient/.gitconfig
</code></pre>
<p>If a repository matches the path after <code>gitdir</code>, the configuration file specified after <code>path</code> will be evaluated and will override any configuration values defined earlier in your global configuration file. If you&#39;re in a case-insensitive file system, you might want to use <code>gitdir/i</code> instead of <code>gitdir</code> to make the path comparison case-insensitive.</p>
<p>For now, I&#39;m only using conditional includes for user and email configuration when I need to change it for all repositories of a certain client. But it&#39;s a very flexible tool in the toolbelt, so I might find another use for it in the future.</p>
]]></description>
            <link>https://www.damirscorner.com/blog/posts/20251114-PerFolderGitConfiguration.html</link>
            <guid isPermaLink="true">https://www.damirscorner.com/blog/posts/20251114-PerFolderGitConfiguration.html</guid>
            <dc:creator><![CDATA[Damir Arh]]></dc:creator>
            <pubDate>Fri, 14 Nov 2025 00:00:00 GMT</pubDate>
        </item>
    </channel>
</rss>