<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://williamforney.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://williamforney.com/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-10-24T18:57:19-07:00</updated><id>https://williamforney.com/feed.xml</id><title type="html">William Forney</title><subtitle>Personal blog of William Forney — software engineer, architect, and developer. Writing about .NET, cloud, architecture, and more.</subtitle><entry><title type="html">Happy 4th of July</title><link href="https://williamforney.com/2026/07/04/happy-4th-of-july/" rel="alternate" type="text/html" title="Happy 4th of July" /><published>2026-07-04T00:00:00-07:00</published><updated>2026-07-04T00:00:00-07:00</updated><id>https://williamforney.com/2026/07/04/happy-4th-of-july</id><content type="html" xml:base="https://williamforney.com/2026/07/04/happy-4th-of-july/"><![CDATA[<p>Happy 4th of July!</p>

<p>Today marks the 250th anniversary of the Declaration of Independence. Wishing everyone a safe and meaningful Independence Day.</p>]]></content><author><name></name></author><category term="Personal" /><category term="holiday" /><category term="Independence Day" /><category term="USA" /><summary type="html"><![CDATA[Happy 4th of July!]]></summary></entry><entry><title type="html">Render GitHub Copilot CLI’s Statusline with Oh My Posh</title><link href="https://williamforney.com/2026/05/14/setup-oh-my-posh-with-github-copilot/" rel="alternate" type="text/html" title="Render GitHub Copilot CLI’s Statusline with Oh My Posh" /><published>2026-05-14T00:00:00-07:00</published><updated>2026-05-14T00:00:00-07:00</updated><id>https://williamforney.com/2026/05/14/setup-oh-my-posh-with-github-copilot</id><content type="html" xml:base="https://williamforney.com/2026/05/14/setup-oh-my-posh-with-github-copilot/"><![CDATA[<h1 id="render-github-copilot-clis-statusline-with-oh-my-posh">Render GitHub Copilot CLI’s Statusline with Oh My Posh</h1>

<p>GitHub Copilot CLI supports a custom statusline command. You can use that hook to run a PowerShell script, map Copilot session data to environment variables, and render the output with Oh My Posh.</p>

<p>This is the working Windows setup I use locally.</p>

<h2 id="prerequisites">Prerequisites</h2>

<ul>
  <li>GitHub Copilot CLI</li>
  <li>PowerShell 7+</li>
  <li>Oh My Posh installed and on <code class="language-plaintext highlighter-rouge">PATH</code></li>
  <li>Nerd Font (recommended for glyphs)</li>
</ul>

<p>Quick check:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="n">oh-my-posh</span><span class="w"> </span><span class="nx">version</span><span class="w">
</span><span class="n">gh</span><span class="w"> </span><span class="nx">copilot</span><span class="w"> </span><span class="nt">--help</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="1-create-the-copilot-config-folder">1. Create the Copilot config folder</h2>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">New-Item</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="s2">\.copilot"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-Null</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="2-create-the-statusline-command-wrapper">2. Create the statusline command wrapper</h2>

<p>Create <code class="language-plaintext highlighter-rouge">%USERPROFILE%\.copilot\statusline.cmd</code>:</p>

<pre><code class="language-cmd">@echo off
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0statusline.ps1"
</code></pre>

<h2 id="3-create-the-powershell-renderer">3. Create the PowerShell renderer</h2>

<p>Create <code class="language-plaintext highlighter-rouge">%USERPROFILE%\.copilot\statusline.ps1</code>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
</pre></td><td class="rouge-code"><pre><span class="bp">$Error</span><span class="n">ActionPreference</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Stop'</span><span class="w">
</span><span class="p">[</span><span class="n">Console</span><span class="p">]::</span><span class="n">OutputEncoding</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.Text.UTF8Encoding</span><span class="p">]::</span><span class="n">new</span><span class="p">()</span><span class="w">

</span><span class="c"># Cache directory for performance optimization</span><span class="w">
</span><span class="nv">$cacheDir</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="s2">\.copilot\.cache"</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$cacheDir</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nv">$cacheDir</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-Null</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Format-TokenCount</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">([</span><span class="n">Nullable</span><span class="p">[</span><span class="n">double</span><span class="p">]]</span><span class="nv">$Value</span><span class="p">)</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$Value</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="s1">'?'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$Value</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="mi">1000000</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="p">(</span><span class="s1">'{0:0.0}m'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="p">(</span><span class="nv">$Value</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1000000</span><span class="p">))</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$Value</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="mi">1000</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="p">(</span><span class="s1">'{0:0.0}k'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="p">(</span><span class="nv">$Value</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">1000</span><span class="p">))</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">([</span><span class="n">int</span><span class="p">]</span><span class="nv">$Value</span><span class="p">)</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Format-Duration</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">([</span><span class="n">Nullable</span><span class="p">[</span><span class="n">double</span><span class="p">]]</span><span class="nv">$Milliseconds</span><span class="p">)</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$Milliseconds</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$Milliseconds</span><span class="w"> </span><span class="o">-le</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="s1">'00:00:00'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="nv">$duration</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">TimeSpan</span><span class="p">]::</span><span class="n">FromMilliseconds</span><span class="p">(</span><span class="nv">$Milliseconds</span><span class="p">)</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="s1">'{0:00}:{1:00}:{2:00}'</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$duration</span><span class="o">.</span><span class="nf">TotalHours</span><span class="p">,</span><span class="w"> </span><span class="nv">$duration</span><span class="o">.</span><span class="nf">Minutes</span><span class="p">,</span><span class="w"> </span><span class="nv">$duration</span><span class="o">.</span><span class="nf">Seconds</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">New-Gauge</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">([</span><span class="n">Nullable</span><span class="p">[</span><span class="n">double</span><span class="p">]]</span><span class="nv">$Percent</span><span class="p">)</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="nv">$Percent</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="kr">return</span><span class="w"> </span><span class="s1">'..........'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="nv">$bounded</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">Math</span><span class="p">]::</span><span class="n">Max</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">Math</span><span class="p">]::</span><span class="n">Min</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">Math</span><span class="p">]::</span><span class="n">Round</span><span class="p">(</span><span class="nv">$Percent</span><span class="p">)))</span><span class="w">
    </span><span class="nv">$filled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">][</span><span class="n">Math</span><span class="p">]::</span><span class="n">Floor</span><span class="p">(</span><span class="nv">$bounded</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nx">10</span><span class="p">)</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">(</span><span class="s1">'#'</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nv">$filled</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="s1">'.'</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">(</span><span class="mi">10</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nv">$filled</span><span class="p">))</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$payload</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">Console</span><span class="p">]::</span><span class="n">In.ReadToEnd</span><span class="p">()</span><span class="w">

</span><span class="c"># Cache check - return cached output if payload hasn't changed</span><span class="w">
</span><span class="nv">$payloadHash</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Get-FileHash</span><span class="w"> </span><span class="nt">-InputStream</span><span class="w"> </span><span class="p">([</span><span class="n">System.IO.MemoryStream</span><span class="p">]::</span><span class="n">new</span><span class="p">([</span><span class="n">System.Text.Encoding</span><span class="p">]::</span><span class="n">UTF8.GetBytes</span><span class="p">(</span><span class="nv">$payload</span><span class="p">)))</span><span class="w"> </span><span class="nt">-Algorithm</span><span class="w"> </span><span class="n">SHA256</span><span class="p">)</span><span class="o">.</span><span class="nf">Hash</span><span class="w">
</span><span class="nv">$cacheFile</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$cacheDir</span><span class="w"> </span><span class="s2">"payload-</span><span class="nv">$payloadHash</span><span class="s2">.txt"</span><span class="w">
</span><span class="nv">$cacheOutput</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="nv">$cacheDir</span><span class="w"> </span><span class="s2">"output-</span><span class="nv">$payloadHash</span><span class="s2">.txt"</span><span class="w">

</span><span class="kr">if</span><span class="w"> </span><span class="p">((</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$cacheFile</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="p">(</span><span class="n">Test-Path</span><span class="w"> </span><span class="nv">$cacheOutput</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="nt">-NoNewline</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Content</span><span class="w"> </span><span class="nv">$cacheOutput</span><span class="p">)</span><span class="w">
    </span><span class="kr">exit</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$json</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$payload</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ConvertFrom-Json</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Write-Host</span><span class="w"> </span><span class="nt">-NoNewline</span><span class="w"> </span><span class="s1">'Copilot status unavailable'</span><span class="w">
    </span><span class="kr">exit</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$context</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">context_window</span><span class="w">
</span><span class="nv">$cost</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">cost</span><span class="w">

</span><span class="nv">$currentTokens</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$context</span><span class="o">.</span><span class="nf">current_context_tokens</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">double</span><span class="p">]</span><span class="nv">$context</span><span class="o">.</span><span class="nf">current_context_tokens</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="bp">$null</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$contextLimit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$context</span><span class="o">.</span><span class="nf">displayed_context_limit</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">double</span><span class="p">]</span><span class="nv">$context</span><span class="o">.</span><span class="nf">displayed_context_limit</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="bp">$null</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$contextPercent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$context</span><span class="o">.</span><span class="nf">current_context_used_percentage</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">double</span><span class="p">]</span><span class="nv">$context</span><span class="o">.</span><span class="nf">current_context_used_percentage</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$context</span><span class="o">.</span><span class="nf">used_percentage</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">double</span><span class="p">]</span><span class="nv">$context</span><span class="o">.</span><span class="nf">used_percentage</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="bp">$null</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$linesAdded</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$cost</span><span class="o">.</span><span class="nf">total_lines_added</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$cost</span><span class="o">.</span><span class="nf">total_lines_added</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$linesRemoved</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$cost</span><span class="o">.</span><span class="nf">total_lines_removed</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$cost</span><span class="o">.</span><span class="nf">total_lines_removed</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="nv">$tokenPercent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$currentTokens</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$contextLimit</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$contextLimit</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">Math</span><span class="p">]::</span><span class="n">Round</span><span class="p">((</span><span class="nv">$currentTokens</span><span class="w"> </span><span class="n">/</span><span class="w"> </span><span class="nv">$contextLimit</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">100</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="mi">0</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'GPT-4'</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="w"> </span><span class="o">-is</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">])</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="o">.</span><span class="nf">display_name</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="o">.</span><span class="nf">display_name</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="o">.</span><span class="nf">id</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="o">.</span><span class="nf">id</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$model</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$json</span><span class="o">.</span><span class="nf">model</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="nv">$copilotError</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$json</span><span class="o">.</span><span class="nf">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">error</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="bp">$null</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$copilotError</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"ERROR: </span><span class="nv">$copilotError</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"ready"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$fileCount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$json</span><span class="o">.</span><span class="nf">files</span><span class="w"> </span><span class="o">-is</span><span class="w"> </span><span class="p">[</span><span class="n">array</span><span class="p">])</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">files</span><span class="o">.</span><span class="nf">Count</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">elseif</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">files</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$responseMs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$null</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">response_time_ms</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">[</span><span class="n">int</span><span class="p">]</span><span class="nv">$json</span><span class="o">.</span><span class="nf">response_time_ms</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_CONTEXT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="si">$(</span><span class="n">Format-TokenCount</span><span class="w"> </span><span class="nv">$currentTokens</span><span class="p">)</span><span class="s2">/</span><span class="si">$(</span><span class="n">Format-TokenCount</span><span class="w"> </span><span class="nv">$contextLimit</span><span class="p">)</span><span class="s2">"</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_GAUGE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Gauge</span><span class="w"> </span><span class="nv">$contextPercent</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_DURATION</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Format-Duration</span><span class="w"> </span><span class="nv">$cost</span><span class="o">.</span><span class="nf">total_duration_ms</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_CHANGES</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$linesAdded</span><span class="w"> </span><span class="o">-or</span><span class="w"> </span><span class="nv">$linesRemoved</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"+</span><span class="nv">$linesAdded</span><span class="s2">/-</span><span class="nv">$linesRemoved</span><span class="s2">"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s1">''</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_TOKEN_PERCENT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$tokenPercent</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_MODEL</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$model</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$status</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_FILE_COUNT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$fileCount</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_RESPONSE_MS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$responseMs</span><span class="w">

</span><span class="nv">$theme</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Join-Path</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="w"> </span><span class="s1">'statusline.omp.json'</span><span class="w">
</span><span class="nv">$cwd</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$json</span><span class="o">.</span><span class="nf">cwd</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$json</span><span class="o">.</span><span class="nf">cwd</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Location</span><span class="p">)</span><span class="o">.</span><span class="nf">Path</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$output</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">oh-my-posh</span><span class="w"> </span><span class="nx">print</span><span class="w"> </span><span class="nx">primary</span><span class="w"> </span><span class="nt">--config</span><span class="w"> </span><span class="nv">$theme</span><span class="w"> </span><span class="nt">--pwd</span><span class="w"> </span><span class="nv">$cwd</span><span class="w"> </span><span class="nt">--force</span><span class="w"> </span><span class="nt">--escape</span><span class="o">=</span><span class="n">false</span><span class="w"> </span><span class="nx">2</span><span class="err">&gt;</span><span class="bp">$null</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">([</span><span class="n">string</span><span class="p">]::</span><span class="n">IsNullOrWhiteSpace</span><span class="p">(</span><span class="nv">$output</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">throw</span><span class="w"> </span><span class="s1">'Oh My Posh returned no output.'</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$cacheFile</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$payload</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-Null</span><span class="w">
    </span><span class="nx">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$cacheOutput</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$output</span><span class="o">.</span><span class="nf">TrimEnd</span><span class="p">()</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">Out-Null</span><span class="w">

    </span><span class="n">Write-Host</span><span class="w"> </span><span class="nt">-NoNewline</span><span class="w"> </span><span class="nv">$output</span><span class="o">.</span><span class="nf">TrimEnd</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">catch</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$segments</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="s2">"🤖 </span><span class="si">$(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_MODEL</span><span class="si">)</span><span class="s2">"</span><span class="w">
        </span><span class="s2">"ctx </span><span class="si">$(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_CONTEXT</span><span class="si">)</span><span class="s2">"</span><span class="w">
        </span><span class="s2">"</span><span class="si">$(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_GAUGE</span><span class="si">)</span><span class="s2">"</span><span class="w">
        </span><span class="s2">"⏱ </span><span class="si">$(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_DURATION</span><span class="si">)</span><span class="s2">"</span><span class="w">
    </span><span class="p">)</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_FILE_COUNT</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$segments</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">"📄 </span><span class="si">$(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_FILE_COUNT</span><span class="si">)</span><span class="s2">"</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_CHANGES</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$segments</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS_CHANGES</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS</span><span class="w"> </span><span class="o">-ne</span><span class="w"> </span><span class="s1">'ready'</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$segments</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="s2">"⚠️ </span><span class="si">$(</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">COPILOT_STATUS</span><span class="si">)</span><span class="s2">"</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="nv">$fallback</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$segments</span><span class="w"> </span><span class="o">-join</span><span class="w"> </span><span class="s2">" | "</span><span class="w">

    </span><span class="n">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$cacheFile</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$payload</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-Null</span><span class="w">
    </span><span class="nx">Set-Content</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$cacheOutput</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$fallback</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-Null</span><span class="w">

    </span><span class="n">Write-Host</span><span class="w"> </span><span class="nt">-NoNewline</span><span class="w"> </span><span class="nv">$fallback</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="4-create-the-statusline-theme-plain-segments--right-end-triangle">4. Create the statusline theme (plain segments + right-end triangle)</h2>

<p>Create <code class="language-plaintext highlighter-rouge">%USERPROFILE%\.copilot\statusline.omp.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"$schema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
  </span><span class="nl">"final_space"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
  </span><span class="nl">"blocks"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prompt"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"alignment"</span><span class="p">:</span><span class="w"> </span><span class="s2">"left"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"segments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#ffffff"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#c386f1"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">" </span><span class="se">\u</span><span class="s2">f489 Copilot "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#ffffff"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#ff479c"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">" 🤖  "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#ffffff"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#6CA35E"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">" </span><span class="se">\u</span><span class="s2">ea83 ctx  (%) "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#193549"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#fffb38"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">"  "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#ffffff"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#8ED1F7"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">" </span><span class="se">\u</span><span class="s2">eba2  "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#111111"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#6CA35E"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">" 📄  "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#111111"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#FF9248"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">" ⚡ ms "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#ffffff"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#00897b"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">"   ✓  ⚠️ "</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"style"</span><span class="p">:</span><span class="w"> </span><span class="s2">"plain"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"foreground"</span><span class="p">:</span><span class="w"> </span><span class="s2">"#00897b"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"background"</span><span class="p">:</span><span class="w"> </span><span class="s2">"transparent"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"template"</span><span class="p">:</span><span class="w"> </span><span class="s2">"</span><span class="se">\u</span><span class="s2">e0b0"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="5-enable-statusline-in-copilot-settings">5. Enable statusline in Copilot settings</h2>

<p>In <code class="language-plaintext highlighter-rouge">%USERPROFILE%\.copilot\settings.json</code>:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w">
  </span><span class="nl">"statusLine"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"command"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"%USERPROFILE%</span><span class="se">\\</span><span class="s2">.copilot</span><span class="se">\\</span><span class="s2">statusline.cmd"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"padding"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"feature_flags"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"enabled"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"STATUS_LINE"</span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>If you prefer, you can use an absolute path instead of <code class="language-plaintext highlighter-rouge">%USERPROFILE%</code>.</p>

<h2 id="6-restart-copilot-cli">6. Restart Copilot CLI</h2>

<p>Close Copilot CLI and launch it again:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="n">gh</span><span class="w"> </span><span class="nx">copilot</span><span class="w">
</span></pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="troubleshooting">Troubleshooting</h2>

<p><strong>No statusline appears</strong></p>
<ul>
  <li>Confirm <code class="language-plaintext highlighter-rouge">STATUS_LINE</code> exists in <code class="language-plaintext highlighter-rouge">feature_flags.enabled</code></li>
  <li>Confirm <code class="language-plaintext highlighter-rouge">statusLine.enabled</code> is <code class="language-plaintext highlighter-rouge">true</code></li>
  <li>Confirm <code class="language-plaintext highlighter-rouge">statusLine.command</code> points to <code class="language-plaintext highlighter-rouge">statusline.cmd</code></li>
</ul>

<p><strong>Broken output</strong></p>
<ul>
  <li>Run this manually to verify rendering:
<code class="language-plaintext highlighter-rouge">oh-my-posh print primary --config "$env:USERPROFILE\.copilot\statusline.omp.json" --force</code></li>
  <li>Confirm script exists:
<code class="language-plaintext highlighter-rouge">Test-Path "$env:USERPROFILE\.copilot\statusline.ps1"</code></li>
</ul>

<p><strong>Slow rendering</strong></p>
<ul>
  <li>Clear cache once if needed:
<code class="language-plaintext highlighter-rouge">Remove-Item "$env:USERPROFILE\.copilot\.cache\*" -Force -ErrorAction SilentlyContinue</code></li>
</ul>

<p>That’s it. You now have a Copilot statusline rendered by Oh My Posh with the same working theme and script used in this post.</p>]]></content><author><name></name></author><category term="Development" /><category term="Terminal" /><category term="GitHub Copilot" /><category term="GitHub Copilot" /><category term="Oh My Posh" /><category term="PowerShell" /><category term="CLI" /><category term="Statusline" /><summary type="html"><![CDATA[Render GitHub Copilot CLI’s Statusline with Oh My Posh]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://williamforney.com/assets/img/posts/oh-my-posh-copilot.jpg" /><media:content medium="image" url="https://williamforney.com/assets/img/posts/oh-my-posh-copilot.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Update on .NET Packages for Shared Code</title><link href="https://williamforney.com/2025/02/23/update-on-net-packages-for-shared-code/" rel="alternate" type="text/html" title="Update on .NET Packages for Shared Code" /><published>2025-02-23T00:00:00-08:00</published><updated>2025-02-23T00:00:00-08:00</updated><id>https://williamforney.com/2025/02/23/update-on-net-packages-for-shared-code</id><content type="html" xml:base="https://williamforney.com/2025/02/23/update-on-net-packages-for-shared-code/"><![CDATA[<p>Hello all, it’s been a while. I have fixed the build for my <a href="https://github.com/improvgroup/sharedcode">improvgroup/sharedcode on GitHub</a> <a href="https://www.nuget.org/profiles/wforney">NuGet packages</a> and pushed a new package update targeting .NET 9 primarily. It should still work with .NET 8 as well. I dropped support for 6 and 7 since they are also out of support from Microsoft at this point. The .NET Core releases are going out much faster than in the old days, so I’ll try to keep these libraries relevant and working in newer versions. I may split some functionality and release a .NET Standard package that could support older projects when I have some free time.</p>

<p>Contributions and suggestions are always welcome. I try to add functionality that complements what’s out there and remove things that I find are better left to other libraries or the framework itself.</p>

<p>That is all for now. Please feel free to leave me issues or feedback on <a href="https://github.com/improvgroup/sharedcode">improvgroup/sharedcode on GitHub</a>.</p>]]></content><author><name></name></author><category term="Development" /><category term="Computers and Internet" /><category term=".NET" /><category term="GitHub" /><category term="NuGet" /><category term="shared code" /><summary type="html"><![CDATA[Hello all, it’s been a while. I have fixed the build for my improvgroup/sharedcode on GitHub NuGet packages and pushed a new package update targeting .NET 9 primarily. It should still work with .NET 8 as well. I dropped support for 6 and 7 since they are also out of support from Microsoft at this point. The .NET Core releases are going out much faster than in the old days, so I’ll try to keep these libraries relevant and working in newer versions. I may split some functionality and release a .NET Standard package that could support older projects when I have some free time.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://williamforney.com/assets/img/posts/pexels-photo-270348.jpeg" /><media:content medium="image" url="https://williamforney.com/assets/img/posts/pexels-photo-270348.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How to map an IDataRecord to an entity class object using expressions</title><link href="https://williamforney.com/2020/09/27/how-to-map-an-idatarecord-to-an-entity-class-object-without-using-reflection/" rel="alternate" type="text/html" title="How to map an IDataRecord to an entity class object using expressions" /><published>2020-09-27T00:00:00-07:00</published><updated>2020-09-27T00:00:00-07:00</updated><id>https://williamforney.com/2020/09/27/how-to-map-an-idatarecord-to-an-entity-class-object-without-using-reflection</id><content type="html" xml:base="https://williamforney.com/2020/09/27/how-to-map-an-idatarecord-to-an-entity-class-object-without-using-reflection/"><![CDATA[<p>Recently I had a need to map an IDataRecord to an entity class object in C#. Here is the code for reference… First, we need the method that generates a function to map the properties while handling DBNull.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
</pre></td><td class="rouge-code"><pre>        private static Func&lt;IDataReader, T&gt; GenerateMapFunction&lt;T&gt;(IDataReader dataReader)
        {
            if (dataReader is null)
            {
                throw new ArgumentNullException(nameof(dataReader));
            }

            var expressions = new List&lt;Expression&gt;();

            var dataReaderParameterExpression = Expression.Parameter(typeof(IDataRecord), "o7thDR");

            var targetExpression = Expression.Variable(typeof(T));

            expressions.Add(Expression.Assign(targetExpression, Expression.New(targetExpression.Type)));

            // does int based lookup
            var indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) });

            var columnNames = Enumerable.Range(0, dataReader.FieldCount)
                                        .Select(index =&gt; new { index, name = dataReader.GetName(index) });

            foreach (var column in columnNames)
            {
                var property = targetExpression.Type.GetProperty(
                    column.name,
                    BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (property is null)
                {
                    continue;
                }

                // index
                var columnIndexExpression = Expression.Constant(column.index);

                // reader
                var readerPropertyExpression = Expression.MakeIndex(
                    dataReaderParameterExpression, indexerInfo, new[] { columnIndexExpression });

                // reader.IsDBNull(index);
                var isReaderDbNull = Expression.Call(
                    dataReaderParameterExpression, nameof(IDataReader.IsDBNull), null, columnIndexExpression);

                // reader as PropertyType;
                var safeCastExpression = Expression.TypeAs(
                    readerPropertyExpression,
                    property.PropertyType);

                // T.Property
                var targetPropertyExpression = Expression.Property(targetExpression, property);

                // T.Property = reader.IsDBNull(index) ? default(PropertyType) :
                // (PropertyType)reader as PropertyType;
                var assignmentBlock = Expression.Condition(
                    Expression.IsTrue(isReaderDbNull),
                    Expression.Assign(
                        targetPropertyExpression,
                        Expression.Default(property.PropertyType)),
                    Expression.Assign(
                        targetPropertyExpression,
                        Expression.Convert(safeCastExpression, property.PropertyType)));

                expressions.Add(assignmentBlock);
            }

            expressions.Add(targetExpression);

            return Expression.Lambda&lt;Func&lt;IDataReader, T&gt;&gt;(
                Expression.Block(
                    new[] { targetExpression },
                    expressions),
                dataReaderParameterExpression).Compile();
        }
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Next we need the function that calls the above…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre>        public static T? Map&lt;T&gt;(this IDataReader dataReader) where T : class
        {
            if (dataReader is null)
            {
                return default;
            }

            //// Use the following loop for debugging field data types in your entity classes.
            //for (var i = 0; i &lt; dataReader.FieldCount; i++)
            //{
            //    Trace.TraceInformation($"{i}:{dataReader.GetName(i)} = {dataReader.GetFieldType(i).Name}");
            //}

            var converter = GenerateMapFunction&lt;T&gt;(dataReader);
            return converter(dataReader);
        }
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This does the job for a single row, but we want all the rows in the reader, right? For that we need this one…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre>        public static List&lt;T&gt; ToList&lt;T&gt;(this IDataReader dataReader) where T : class  
        {  
            var list = new List&lt;T&gt;();  
  
            if (dataReader is not null)  
            {  
                while (dataReader.Read())  
                {  
                    var row = Map&lt;T&gt;(dataReader);  
                    if (row is not null)  
                    {  
                        list.Add(row);  
                    }  
                }  
            }  
  
            return list;  
        }
</pre></td></tr></tbody></table></code></pre></div></div>

<p>That’s it… It doesn’t use the async API of the data reader for the read… We might want an async version too…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre>        public static async Task&lt;List&lt;T&gt;&gt; ToListAsync&lt;T&gt;(  
            this DbDataReader dataReader,  
            CancellationToken cancellationToken = default) where T : class  
        {  
            var list = new List&lt;T&gt;();  
  
            if (dataReader is not null)  
            {  
                while (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))  
                {  
                    var row = Map&lt;T&gt;(dataReader);  
                    if (row is not null)  
                    {  
                        list.Add(row);  
                    }  
                }  
            }  
  
            return list;  
        }  
</pre></td></tr></tbody></table></code></pre></div></div>

<p>But what if we don’t want to pull the whole list all at once? Then you need this…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre>        public static async IAsyncEnumerable&lt;T&gt; EnumerateAsync&lt;T&gt;(  
            this DbDataReader dataReader,  
             CancellationToken cancellationToken = default) where T : class  
        {  
            if (dataReader is null)  
            {  
                yield break;  
            }  
  
            while (await dataReader.ReadAsync(cancellationToken).ConfigureAwait(false))  
            {  
                var row = Map&lt;T&gt;(dataReader);  
                if (row is not null)  
                {  
                    yield return row;  
                }  
            }  
        }  
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Oh, but what if you want a blocking version of that?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre>        public static IEnumerable&lt;T&gt; Enumerate&lt;T&gt;(this DbDataReader dataReader) where T : class  
        {  
            if (dataReader is null)  
            {  
                yield break;  
            }  
  
            while (dataReader.Read())  
            {  
                var row = Map&lt;T&gt;(dataReader);  
                if (row is not null)  
                {  
                    yield return row;  
                }  
            }  
        }  
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Hopefully, part of this will come in handy for someone as it was for me.</p>

<p>The code for this is available on <a href="https://github.com/improvgroup/sharedcode">improvgroup/sharedcode on GitHub</a> and via <a href="https://www.nuget.org/packages/SharedCode.Data/">SharedCode.Data on NuGet</a>.</p>]]></content><author><name></name></author><category term="Development" /><category term="Computers and Internet" /><category term="C#" /><category term="DbDataRecord" /><category term="DBNull" /><category term="IDataRecord" /><category term="Map" /><summary type="html"><![CDATA[Recently I had a need to map an IDataRecord to an entity class object in C#. Here is the code for reference… First, we need the method that generates a function to map the properties while handling DBNull. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 private static Func&lt;IDataReader, T&gt; GenerateMapFunction&lt;T&gt;(IDataReader dataReader) { if (dataReader is null) { throw new ArgumentNullException(nameof(dataReader)); } var expressions = new List&lt;Expression&gt;(); var dataReaderParameterExpression = Expression.Parameter(typeof(IDataRecord), "o7thDR"); var targetExpression = Expression.Variable(typeof(T)); expressions.Add(Expression.Assign(targetExpression, Expression.New(targetExpression.Type))); // does int based lookup var indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) }); var columnNames = Enumerable.Range(0, dataReader.FieldCount) .Select(index =&gt; new { index, name = dataReader.GetName(index) }); foreach (var column in columnNames) { var property = targetExpression.Type.GetProperty( column.name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (property is null) { continue; } // index var columnIndexExpression = Expression.Constant(column.index); // reader var readerPropertyExpression = Expression.MakeIndex( dataReaderParameterExpression, indexerInfo, new[] { columnIndexExpression }); // reader.IsDBNull(index); var isReaderDbNull = Expression.Call( dataReaderParameterExpression, nameof(IDataReader.IsDBNull), null, columnIndexExpression); // reader as PropertyType; var safeCastExpression = Expression.TypeAs( readerPropertyExpression, property.PropertyType); // T.Property var targetPropertyExpression = Expression.Property(targetExpression, property); // T.Property = reader.IsDBNull(index) ? default(PropertyType) : // (PropertyType)reader as PropertyType; var assignmentBlock = Expression.Condition( Expression.IsTrue(isReaderDbNull), Expression.Assign( targetPropertyExpression, Expression.Default(property.PropertyType)), Expression.Assign( targetPropertyExpression, Expression.Convert(safeCastExpression, property.PropertyType))); expressions.Add(assignmentBlock); } expressions.Add(targetExpression); return Expression.Lambda&lt;Func&lt;IDataReader, T&gt;&gt;( Expression.Block( new[] { targetExpression }, expressions), dataReaderParameterExpression).Compile(); }]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://williamforney.com/assets/img/posts/pexels-photo-546819.jpeg" /><media:content medium="image" url="https://williamforney.com/assets/img/posts/pexels-photo-546819.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">STAThread async/await got you down?</title><link href="https://williamforney.com/2020/09/27/stathread-async-await-got-you-down/" rel="alternate" type="text/html" title="STAThread async/await got you down?" /><published>2020-09-27T00:00:00-07:00</published><updated>2020-09-27T00:00:00-07:00</updated><id>https://williamforney.com/2020/09/27/stathread-async-await-got-you-down</id><content type="html" xml:base="https://williamforney.com/2020/09/27/stathread-async-await-got-you-down/"><![CDATA[<p>When encountering issues with async/await while building a Windows Forms application recently I came across this helpful class that allows you to await returning to the initial (usually UI) thread…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre>public struct SynchronizationContextAwaiter : INotifyCompletion, IEquatable&lt;SynchronizationContextAwaiter&gt;
    {
        private static readonly SendOrPostCallback postCallback = state =&gt; (state as Action)?.Invoke();

        private readonly SynchronizationContext context;

        public SynchronizationContextAwaiter(SynchronizationContext context) =&gt; this.context = context;

        public bool IsCompleted =&gt; context == SynchronizationContext.Current;

        public static bool operator !=(SynchronizationContextAwaiter left, SynchronizationContextAwaiter right) =&gt;
            !(left == right);

        public static bool operator ==(SynchronizationContextAwaiter left, SynchronizationContextAwaiter right) =&gt;
            left.Equals(right);

        public override bool Equals(object? obj) =&gt;
            obj is SynchronizationContextAwaiter awaiter &amp;&amp; this.Equals(awaiter);

        public bool Equals(SynchronizationContextAwaiter other) =&gt;
            EqualityComparer&lt;SynchronizationContext&gt;.Default.Equals(this.context, other.context);

        public override int GetHashCode() =&gt; HashCode.Combine(this.context);

        [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Reviewed.")]
        public void GetResult()
        {
        }

        public void OnCompleted(Action continuation) =&gt; context.Post(postCallback, continuation);
    }
</pre></td></tr></tbody></table></code></pre></div></div>

<p>It goes along with this extension method…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre>        /// &lt;summary&gt;
        /// Gets an awaiter for the specified synchronization context.
        /// &lt;/summary&gt;
        /// &lt;param name="context"&gt;The synchronization context.&lt;/param&gt;
        /// &lt;returns&gt;The &lt;see cref="SynchronizationContextAwaiter"/&gt;.&lt;/returns&gt;
        public static SynchronizationContextAwaiter GetAwaiter(this SynchronizationContext context) =&gt;
            new SynchronizationContextAwaiter(context);
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And you use it like this…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre>                var syncContext = SynchronizationContext.Current; // Grab UI thread

                // TODO: Do long running async stuff here

                if (syncContext != null)
                {
                    await syncContext;
                }

                // TODO: Do original/UI thread stuff here
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This is a somewhat cleaner way to handle the multi-threading problem in async methods in a STAThread application.</p>]]></content><author><name></name></author><category term="Development" /><category term="Computers and Internet" /><category term="async" /><category term="await" /><category term="C#" /><category term="STA" /><category term="SynchronizationContext" /><category term="Windows Forms" /><summary type="html"><![CDATA[When encountering issues with async/await while building a Windows Forms application recently I came across this helpful class that allows you to await returning to the initial (usually UI) thread… 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public struct SynchronizationContextAwaiter : INotifyCompletion, IEquatable&lt;SynchronizationContextAwaiter&gt; { private static readonly SendOrPostCallback postCallback = state =&gt; (state as Action)?.Invoke(); private readonly SynchronizationContext context; public SynchronizationContextAwaiter(SynchronizationContext context) =&gt; this.context = context; public bool IsCompleted =&gt; context == SynchronizationContext.Current; public static bool operator !=(SynchronizationContextAwaiter left, SynchronizationContextAwaiter right) =&gt; !(left == right); public static bool operator ==(SynchronizationContextAwaiter left, SynchronizationContextAwaiter right) =&gt; left.Equals(right); public override bool Equals(object? obj) =&gt; obj is SynchronizationContextAwaiter awaiter &amp;&amp; this.Equals(awaiter); public bool Equals(SynchronizationContextAwaiter other) =&gt; EqualityComparer&lt;SynchronizationContext&gt;.Default.Equals(this.context, other.context); public override int GetHashCode() =&gt; HashCode.Combine(this.context); [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Reviewed.")] public void GetResult() { } public void OnCompleted(Action continuation) =&gt; context.Post(postCallback, continuation); }]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://williamforney.com/assets/img/posts/pexels-photo-270348.jpeg" /><media:content medium="image" url="https://williamforney.com/assets/img/posts/pexels-photo-270348.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Using a wait cursor doesn’t have to be hard…</title><link href="https://williamforney.com/2020/09/27/using-a-wait-cursor-doesnt-have-to-be-hard/" rel="alternate" type="text/html" title="Using a wait cursor doesn’t have to be hard…" /><published>2020-09-27T00:00:00-07:00</published><updated>2020-09-27T00:00:00-07:00</updated><id>https://williamforney.com/2020/09/27/using-a-wait-cursor-doesnt-have-to-be-hard</id><content type="html" xml:base="https://williamforney.com/2020/09/27/using-a-wait-cursor-doesnt-have-to-be-hard/"><![CDATA[<p>Here is a simple disposable wait cursor class to simplify your mouse display needs…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
</pre></td><td class="rouge-code"><pre>    /// &lt;summary&gt;
    /// The WaitCursor class. Implements the &lt;see cref="IDisposable" /&gt;
    /// &lt;/summary&gt;
    /// &lt;seealso cref="IDisposable" /&gt;
    public class WaitCursor : IDisposable
    {
        /// &lt;summary&gt;
        /// The previous cursor.
        /// &lt;/summary&gt;
        private readonly Cursor previousCursor;

        /// &lt;summary&gt;
        /// A value indicating whether the class has been disposed.
        /// &lt;/summary&gt;
        private bool disposedValue;

        /// &lt;summary&gt;
        /// Initializes a new instance of the &lt;see cref="WaitCursor" /&gt; class.
        /// &lt;/summary&gt;
        public WaitCursor()
        {
            this.previousCursor = Cursor.Current;
            Cursor.Current = Cursors.WaitCursor;
        }

        /// &lt;summary&gt;
        /// Finalizes an instance of the &lt;see cref="WaitCursor" /&gt; class.
        /// &lt;/summary&gt;
        ~WaitCursor()
        {
            this.Dispose(disposing: false);
        }

        /// &lt;summary&gt;
        /// Performs application-defined tasks associated with freeing, releasing, or resetting
        /// unmanaged resources.
        /// &lt;/summary&gt;
        public void Dispose()
        {
            this.Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        /// &lt;summary&gt;
        /// Releases unmanaged and - optionally - managed resources.
        /// &lt;/summary&gt;
        /// &lt;param name="disposing"&gt;
        /// &lt;c&gt;true&lt;/c&gt; to release both managed and unmanaged resources; &lt;c&gt;false&lt;/c&gt; to release
        /// only unmanaged resources.
        /// &lt;/param&gt;
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                if (disposing)
                {
                }

                Cursor.Current = this.previousCursor;

                this.disposedValue = true;
            }
        }
    }
</pre></td></tr></tbody></table></code></pre></div></div>]]></content><author><name></name></author><category term="Development" /><category term="Computers and Internet" /><category term="cursor" /><category term="wait" /><category term="wait cursor" /><category term="WaitCursor" /><category term="Windows Forms" /><summary type="html"><![CDATA[Here is a simple disposable wait cursor class to simplify your mouse display needs… 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 /// &lt;summary&gt; /// The WaitCursor class. Implements the &lt;see cref="IDisposable" /&gt; /// &lt;/summary&gt; /// &lt;seealso cref="IDisposable" /&gt; public class WaitCursor : IDisposable { /// &lt;summary&gt; /// The previous cursor. /// &lt;/summary&gt; private readonly Cursor previousCursor; /// &lt;summary&gt; /// A value indicating whether the class has been disposed. /// &lt;/summary&gt; private bool disposedValue; /// &lt;summary&gt; /// Initializes a new instance of the &lt;see cref="WaitCursor" /&gt; class. /// &lt;/summary&gt; public WaitCursor() { this.previousCursor = Cursor.Current; Cursor.Current = Cursors.WaitCursor; } /// &lt;summary&gt; /// Finalizes an instance of the &lt;see cref="WaitCursor" /&gt; class. /// &lt;/summary&gt; ~WaitCursor() { this.Dispose(disposing: false); } /// &lt;summary&gt; /// Performs application-defined tasks associated with freeing, releasing, or resetting /// unmanaged resources. /// &lt;/summary&gt; public void Dispose() { this.Dispose(disposing: true); GC.SuppressFinalize(this); } /// &lt;summary&gt; /// Releases unmanaged and - optionally - managed resources. /// &lt;/summary&gt; /// &lt;param name="disposing"&gt; /// &lt;c&gt;true&lt;/c&gt; to release both managed and unmanaged resources; &lt;c&gt;false&lt;/c&gt; to release /// only unmanaged resources. /// &lt;/param&gt; protected virtual void Dispose(bool disposing) { if (!this.disposedValue) { if (disposing) { } Cursor.Current = this.previousCursor; this.disposedValue = true; } } }]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://williamforney.com/assets/img/posts/pexels-photo-301448.jpeg" /><media:content medium="image" url="https://williamforney.com/assets/img/posts/pexels-photo-301448.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">…and back to WordPress again…</title><link href="https://williamforney.com/2020/09/12/and-back-to-wordpress-again/" rel="alternate" type="text/html" title="…and back to WordPress again…" /><published>2020-09-12T00:00:00-07:00</published><updated>2020-09-12T00:00:00-07:00</updated><id>https://williamforney.com/2020/09/12/and-back-to-wordpress-again</id><content type="html" xml:base="https://williamforney.com/2020/09/12/and-back-to-wordpress-again/"><![CDATA[<p>A while ago I swapped my blog to BlogEngine.NET, then to MiniBlog, then to Ghost, then back to MiniBlog. Now I’m going back to WordPress again after a decade or so. The other platforms were nice, and some were integrated well, but nobody has kept them up, or they were just side demo projects for people. At this point I wanted a platform that I can host that is relatively stable and is maintained actively. Some of the newer features are nice too.</p>]]></content><author><name></name></author><category term="Computers and Internet" /><category term="Development" /><summary type="html"><![CDATA[A while ago I swapped my blog to BlogEngine.NET, then to MiniBlog, then to Ghost, then back to MiniBlog. Now I’m going back to WordPress again after a decade or so. The other platforms were nice, and some were integrated well, but nobody has kept them up, or they were just side demo projects for people. At this point I wanted a platform that I can host that is relatively stable and is maintained actively. Some of the newer features are nice too.]]></summary></entry><entry><title type="html">Update to ASP.NET Core</title><link href="https://williamforney.com/2018/08/19/update-to-asp-net-core/" rel="alternate" type="text/html" title="Update to ASP.NET Core" /><published>2018-08-19T00:00:00-07:00</published><updated>2018-08-19T00:00:00-07:00</updated><id>https://williamforney.com/2018/08/19/update-to-asp-net-core</id><content type="html" xml:base="https://williamforney.com/2018/08/19/update-to-asp-net-core/"><![CDATA[<p>Recently I updated the blog to MiniBlog.Core on ASP.NET Core. So far it seems pretty fast. I’m working on a new engine for it in my spare time using Azure Functions and blob storage. More to come when I find some time to update this.</p>]]></content><author><name></name></author><category term="Computers and Internet" /><category term="Development" /><category term="ASP.NET Core" /><category term="MiniBlog.Core" /><category term="update" /><category term="ASP.NET" /><summary type="html"><![CDATA[Recently I updated the blog to MiniBlog.Core on ASP.NET Core. So far it seems pretty fast. I’m working on a new engine for it in my spare time using Azure Functions and blob storage. More to come when I find some time to update this.]]></summary></entry><entry><title type="html">Getting Started with Aurelia on ASP.NET Core</title><link href="https://williamforney.com/2017/05/22/getting-started-with-aurelia-on-asp-net-core/" rel="alternate" type="text/html" title="Getting Started with Aurelia on ASP.NET Core" /><published>2017-05-22T00:00:00-07:00</published><updated>2017-05-22T00:00:00-07:00</updated><id>https://williamforney.com/2017/05/22/getting-started-with-aurelia-on-asp-net-core</id><content type="html" xml:base="https://williamforney.com/2017/05/22/getting-started-with-aurelia-on-asp-net-core/"><![CDATA[<p><a href="http://www.aurelia.io/">Aurelia</a>, one of the leading JavaScript client frameworks for single page applications (SPAs), has been around for a while now and there are a number of resources for getting started with it. Rather than try to make yet another demo, I thought it might be fun to create a site repository that could be used as a template with a few more bells and whistles already setup. It is my hope that this will be useful not just to me, but to anyone else who wants to start a project. We’re going to use the command line tools to do this, but you can use <a href="http://www.visualstudio.com/">Visual Studio</a> if you want as well.</p>

<h2 id="setting-up-the-aspnet-core-project">Setting up the ASP.NET Core Project</h2>

<p><strong>Prerequisites</strong></p>

<ul>
  <li><a href="https://nodejs.org/en/">Node.js</a> (preferably current)</li>
  <li><a href="https://www.microsoft.com/net/core#windowscmd">ASP.NET Core SDK</a></li>
</ul>

<p><strong>Steps</strong></p>

<ol>
  <li>Create a folder for your project.</li>
  <li>Open a command prompt (cmd) in that folder.</li>
  <li>
    <p>Install the SPA templates with the following command:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<p><img src="/assets/img/posts/aurelia-step3.png" alt="screenshot" /></p>

<ol>
  <li>
    <p>Create the Aurelia project like this:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>dotnet new Aurelia 
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<p><img src="/assets/img/posts/aurelia-step4.png" alt="screenshot" /></p>

<ol>
  <li>
    <p>Prepare the environment to run using these commands, ignoring any warnings from npm as they are expected…</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>dotnet restore
npm install
setx ASPNETCORE_ENVIRONMENT "Development"
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<p><img src="/assets/img/posts/aurelia-step5a.png" alt="screenshot" />
<img src="/assets/img/posts/aurelia-step5b.png" alt="screenshot" />
<img src="/assets/img/posts/aurelia-step5c.png" alt="screenshot" /></p>

<ol>
  <li>Restart your command prompt to ensure that the environment change takes effect.</li>
  <li>
    <p>Run your new app with the command line</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>dotnet run
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<p><img src="/assets/img/posts/aurelia-step7.png" alt="screenshot" /></p>

<p>These steps should give you a bare bones ASP.NET Core site with a basic Aurelia setup that looks like this…</p>

<p><img src="/assets/img/posts/aurelia-finish.png" alt="screenshot" /></p>

<h2 id="publishing-to-a-host">Publishing to a Host</h2>

<p>Now that you have a working site you’ll want to publish it to an actual server somewhere. To do that you’ll perform a publish. You can do this by running this command:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>dotnet publish -c Release
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This command compiles the server side C# code and runs a <a href="https://webpack.js.org/">webpack</a> production build on the TypeScript and client assets. You can then find the files to upload to your host in the <strong><em>bin\Release\netcoreapp1.1\publish</em></strong> folder.</p>

<h2 id="exploring-the-template">Exploring the Template</h2>

<p>This template takes care of a lot of things that you would have to setup manually and makes a good starting point for adding more functionality, such as logging with Application Insights or another logging provider, fleshing out an administration interface for user management and authentication/authorization using <a href="https://identityserver.io/">IdentityServer</a>, or any number of other useful additions that are widely available.</p>

<p>To get started understanding what we’ve got here, you’ll want to take a look into the files in your new project. You’ll notice that in Startup.cs we have setup a fallback route that allows anything not dealt with by a static file or MVC route to be sent to the Home Index view. This is the secret sauce that lets all your links work even though they aren’t configured individually.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre>routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
</pre></td></tr></tbody></table></code></pre></div></div>

<p>To be continued…</p>]]></content><author><name></name></author><category term="Computers and Internet" /><category term="Development" /><category term="ASP.NET Core" /><category term="Aurelia" /><category term="TypeScript" /><category term="SPA" /><category term="ASP.NET" /><summary type="html"><![CDATA[Aurelia, one of the leading JavaScript client frameworks for single page applications (SPAs), has been around for a while now and there are a number of resources for getting started with it. Rather than try to make yet another demo, I thought it might be fun to create a site repository that could be used as a template with a few more bells and whistles already setup. It is my hope that this will be useful not just to me, but to anyone else who wants to start a project. We’re going to use the command line tools to do this, but you can use Visual Studio if you want as well.]]></summary></entry><entry><title type="html">Aurelia with TypeScript on ASP.NET Core</title><link href="https://williamforney.com/2016/03/08/aurelia-with-typescript-on-asp-net-core/" rel="alternate" type="text/html" title="Aurelia with TypeScript on ASP.NET Core" /><published>2016-03-08T00:00:00-08:00</published><updated>2016-03-08T00:00:00-08:00</updated><id>https://williamforney.com/2016/03/08/aurelia-with-typescript-on-asp-net-core</id><content type="html" xml:base="https://williamforney.com/2016/03/08/aurelia-with-typescript-on-asp-net-core/"><![CDATA[<p>I’ve been tracking the <a href="https://docs.asp.net/en/latest/" title="ASP.NET documentation">ASP.NET</a> betas and release candidates over the past year or more and it’s coming along nicely. I like the separation of client side and server side in the new folder structure and the unification of the <a href="https://docs.asp.net/en/latest/mvc/index.html">ASP.NET MVC</a> and WebAPI controllers. For the past few years I’ve used <a href="http://www.jsviews.com/">jsViews</a>, <a href="http://www.telerik.com/kendo-ui">Kendo UI</a>, <a href="https://angular.io/">Angular</a>, <a href="http://durandaljs.com/">Durandal</a>, <a href="http://knockoutjs.com/">Knockout</a>, and <a href="http://aurelia.io/">Aurelia</a> for front end JavaScript development.</p>

<p>A while ago I was using TypeScript with these to help with intellisense and typing issues across the libraries and sites I worked on. I often find myself introducing whatever team I’m on to these technologies in one way or another. I’ve been working with the combination of ASP.NET Core, formerly ASP.NET 5, and Aurelia with TypeScript and Web Components.</p>

<p>I start with the yoeman generator for ASP.NET and add the pieces as I go. I use ASP.NET MVC for the API and plain old HTML and TypeScript for the UI. The client tool chain consists of the usual npm, jspm, and gulp. When the new ASP.NET bits are done I’ll make a template for all this and post it to github. In the meantime I’m considering doing a short video or slide deck walking through the setup if anyone is interested.</p>]]></content><author><name></name></author><category term="Computers and Internet" /><category term="Development" /><category term="ASP.NET" /><summary type="html"><![CDATA[I’ve been tracking the ASP.NET betas and release candidates over the past year or more and it’s coming along nicely. I like the separation of client side and server side in the new folder structure and the unification of the ASP.NET MVC and WebAPI controllers. For the past few years I’ve used jsViews, Kendo UI, Angular, Durandal, Knockout, and Aurelia for front end JavaScript development.]]></summary></entry></feed>