&lt;?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Articles from 2026 on PowerShell.org - Welcome Automaters!</title><link>https://powershell.org/articles/2026/</link><description>Recent content in Articles from 2026 on PowerShell.org - Welcome Automaters!</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://powershell.org/articles/2026/index.xml" rel="self" type="application/rss+xml"/><item><title>PowerShell Can Put Pictures in Your Terminal with SIXEL</title><link>https://powershell.org/articles/2026-09-03-powershell-can-put-pictures-in-your-terminal-with-sixel/</link><guid>https://powershell.org/articles/2026-09-03-powershell-can-put-pictures-in-your-terminal-with-sixel/</guid><pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;PowerShell normally sends text and objects to a terminal. This experiment sends an image.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Out-Sixel -Path ./sixel-demo.&lt;span style="color:#79c0ff"&gt;svg&lt;/span&gt; -Width &lt;span style="color:#a5d6ff"&gt;480&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Instead of opening Preview or a browser, the command decodes the SVG, converts it into a palette, and writes a stream of terminal escape sequences. iTerm2 interprets those sequences and paints the image directly between the command and the next prompt.&lt;/p&gt;
&lt;p&gt;This is mostly for fun. It is also a useful reminder that a terminal is a protocol endpoint, not merely a grid of characters.&lt;/p&gt;</description><content:encoded>&lt;![CDATA[<p>PowerShell normally sends text and objects to a terminal. This experiment sends an image.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Out-Sixel -Path ./sixel-demo.<span style="color:#79c0ff">svg</span> -Width<span style="color:#a5d6ff">480</span></span></span></code></pre></td></tr></table></div></div><p>Instead of opening Preview or a browser, the command decodes the SVG, converts it into a palette, and writes a stream of terminal escape sequences. iTerm2 interprets those sequences and paints the image directly between the command and the next prompt.</p><p>This is mostly for fun. It is also a useful reminder that a terminal is a protocol endpoint, not merely a grid of characters.</p><div class="ps-alert ps-alert-note" role="note"><p class="ps-alert-title"><i class="fas fa-circle-info" aria-hidden="true"/>Note</p><div class="ps-alert-body"><p><strong>Tested environment:</strong> macOS 26.6.2, iTerm2 3.6.11, PowerShell 7.6.1, Apple Silicon.</p><p>The direct iTerm2 session is the tested path in this article. No tmux or screen sits between PowerShell and the terminal.</p></div></div><p><img src="/images/articles/powershell-sixel/out-sixel-iterm.gif" alt="Out-Sixel rendering an SVG directly inside a PowerShell session in iTerm2"/><p>The recording above is a real iTerm2 session. The command reads the SVG, writes SIXEL escape sequences to the terminal, and returns to the PowerShell prompt after iTerm2 renders the image.</p><p><img src="/images/articles/powershell-sixel/sixel-demo.svg" alt="The PowerShell plus SIXEL SVG used by the terminal demo"/><p>The image above is the source file used in the recording.<a href="/images/articles/powershell-sixel/sixel-demo.svg">Download the demo SVG</a> and save it as<code>sixel-demo.svg</code> to run the opening command.</p><h2 id="what-is-sixel" class="ps-heading">What is SIXEL?<a class="ps-heading-anchor" href="#what-is-sixel" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p><a href="https://vt100.net/docs/vt3xx-gp/chapter14.html">SIXEL</a> is a bitmap graphics format originally used by DEC terminals and printers. The name comes from its basic unit: a character represents a vertical group of six pixels.</p><p>A SIXEL image is still text from the process&rsquo;s point of view. It begins with a device-control escape sequence, contains a palette and encoded pixel bands, and ends with a string terminator. A compatible terminal recognizes that stream as graphics rather than printable characters.</p><p>That old design has one property that remains attractive: the image travels over the same channel as terminal output. There is no separate window, web server, or GUI API.</p><h2 id="the-powershell-experiment" class="ps-heading">The PowerShell experiment<a class="ps-heading-anchor" href="#the-powershell-experiment" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The command is part of an experimental C# port of<a href="https://github.com/saitoha/libsixel">libsixel</a>. My<a href="https://github.com/eosfor/libsixel">C# port repository</a> contains a small PowerShell module whose public surface is the compiled<code>Out-Sixel</code> cmdlet.</p><p>Install the exact<a href="https://github.com/eosfor/libsixel/releases/tag/v0.2.0-beta2">LibSixel.PowerShell 0.2.0-beta2</a> prerelease used by this article from PowerShell Gallery:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">7</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Install-Module `</span></span><span style="display:flex;"><span> -Name LibSixel.<span style="color:#79c0ff">PowerShell</span> `</span></span><span style="display:flex;"><span> -RequiredVersion<span style="color:#a5d6ff">'0.2.0-beta2'</span> `</span></span><span style="display:flex;"><span> -AllowPrerelease `</span></span><span style="display:flex;"><span> -Scope CurrentUser</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Import-Module LibSixel.<span style="color:#79c0ff">PowerShell</span></span></span></code></pre></td></tr></table></div></div><p>Then confirm that PowerShell can see the compiled cmdlet:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Get-Command Out-Sixel</span></span></code></pre></td></tr></table></div></div><p>The cmdlet accepts PNG, JPEG, and SVG files:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Out-Sixel -Path ./photo.<span style="color:#79c0ff">png</span></span></span><span style="display:flex;"><span>Out-Sixel -Path ./photo.<span style="color:#79c0ff">jpg</span></span></span><span style="display:flex;"><span>Out-Sixel -Path ./diagram.<span style="color:#79c0ff">svg</span></span></span></code></pre></td></tr></table></div></div><p>Large images should be resized before encoding.<code>-Width</code> and<code>-Height</code> accept pixel dimensions; specifying only one preserves the aspect ratio:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Out-Sixel -Path ./photo.<span style="color:#79c0ff">jpg</span> -Width<span style="color:#a5d6ff">480</span></span></span><span style="display:flex;"><span>Out-Sixel -Path ./diagram.<span style="color:#79c0ff">svg</span> -Height<span style="color:#a5d6ff">260</span></span></span></code></pre></td></tr></table></div></div><p>SIXEL uses a limited palette. The default is 256 colors, but a smaller palette can reduce the output considerably:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Out-Sixel -Path ./photo.<span style="color:#79c0ff">jpg</span> -Width<span style="color:#a5d6ff">480</span> -Colors<span style="color:#a5d6ff">64</span></span></span></code></pre></td></tr></table></div></div><p>The result will not compete with a normal image viewer. That is part of the charm: the encoder applies color quantization and dithering, giving photographs a slightly retro character while diagrams usually remain crisp.</p><h2 id="render-an-svg-without-creating-a-file" class="ps-heading">Render an SVG without creating a file<a class="ps-heading-anchor" href="#render-an-svg-without-creating-a-file" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p><code>Out-Sixel</code> also recognizes SVG content arriving through the pipeline. This makes a self-contained demo possible:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">17</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">18</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">19</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">20</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">21</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$svg</span> =<span style="color:#79c0ff">@'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">&lt;svg xmlns="http://www.w3.org/2000/svg" width="640" height="240"&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;defs&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;linearGradient id="g" x1="0" x2="1"&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;stop offset="0" stop-color="#38bdf8" /&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;stop offset="1" stop-color="#8b5cf6" /&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;/linearGradient&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;/defs&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;rect width="640" height="240" rx="24" fill="#111827" /&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;text x="320" y="105" text-anchor="middle"</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> font-family="monospace" font-size="38" fill="url(#g)"&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> PowerShell + SIXEL</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;/text&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;text x="320" y="160" text-anchor="middle"</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> font-family="monospace" font-size="20" fill="#cbd5e1"&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> no browser required</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff"> &lt;/text&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">&lt;/svg&gt;</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">'@</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$svg</span> | Out-Sixel -Width<span style="color:#a5d6ff">480</span></span></span></code></pre></td></tr></table></div></div><p>This is an entertaining way to display a generated diagram or status card. It is not a replacement for structured PowerShell output: once data becomes pixels, the pipeline can no longer filter or sort it.</p><h2 id="what-happens-inside-the-command" class="ps-heading">What happens inside the command?<a class="ps-heading-anchor" href="#what-happens-inside-the-command" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The path from a file to the terminal is deliberately small:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>PNG / JPEG / SVG</span></span><span style="display:flex;"><span> |</span></span><span style="display:flex;"><span> v</span></span><span style="display:flex;"><span>SkiaSharp decode or SVG rasterization</span></span><span style="display:flex;"><span> |</span></span><span style="display:flex;"><span> v</span></span><span style="display:flex;"><span>RGBA pixel buffer</span></span><span style="display:flex;"><span> |</span></span><span style="display:flex;"><span> v</span></span><span style="display:flex;"><span>palette selection and dithering</span></span><span style="display:flex;"><span> |</span></span><span style="display:flex;"><span> v</span></span><span style="display:flex;"><span>SIXEL escape sequence</span></span><span style="display:flex;"><span> |</span></span><span style="display:flex;"><span> v</span></span><span style="display:flex;"><span>iTerm2 renders the pixels</span></span></code></pre></td></tr></table></div></div><p>SkiaSharp decodes PNG and JPEG inputs.<code>Svg.Skia</code> rasterizes SVG into the same RGBA representation. The ported libsixel code then selects a palette, applies dithering, and writes the SIXEL device-control string through<code>Host.UI</code>.</p><p>The command can return that string instead of writing it to the terminal:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$sixel</span> = Out-Sixel -Path ./diagram.<span style="color:#79c0ff">svg</span> -Width<span style="color:#a5d6ff">480</span> -AsString</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>[<span style="color:#79c0ff;font-weight:bold">int][char</span>]<span style="color:#79c0ff">$sixel</span>[<span style="color:#a5d6ff">0</span>]</span></span><span style="display:flex;"><span>[<span style="color:#79c0ff;font-weight:bold">int][char</span>]<span style="color:#79c0ff">$sixel</span>[<span style="color:#a5d6ff">1</span>]</span></span></code></pre></td></tr></table></div></div><p>The first two values are<code>27</code> and<code>80</code>:<code>ESC</code> followed by<code>P</code>, the beginning of a device-control string.</p><h2 id="exactly-where-does-it-work" class="ps-heading">Exactly where does it work?<a class="ps-heading-anchor" href="#exactly-where-does-it-work" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Terminal support matters more than the shell prompt. The same<code>pwsh</code> command can display an image in one terminal and produce garbage in another.</p><table><thead><tr><th>Environment</th><th>Status for this experiment</th></tr></thead><tbody><tr><td><strong>iTerm2 3.3 or newer on macOS</strong></td><td>Supported. This article was tested with iTerm2 3.6.11.</td></tr><tr><td><strong>PowerShell 7.4 or newer</strong></td><td>Required by the current<code>net8.0</code> module build. This article was tested with PowerShell 7.6.1.</td></tr><tr><td><strong>Windows Terminal 1.22 or newer</strong></td><td>SIXEL is supported by the terminal, and the module includes Windows Skia native assets. I have not tested this combination yet—please try it and report what you find.</td></tr><tr><td><strong>Windows PowerShell 5.1</strong></td><td>Not supported. It cannot load this<code>net8.0</code> module.</td></tr><tr><td><strong>macOS Terminal.app and the VS Code integrated terminal</strong></td><td>Not tested and not claimed as supported here.</td></tr><tr><td><strong>tmux and screen</strong></td><td>Outside the supported path. A multiplexer may filter the escape sequence or require its own SIXEL configuration.</td></tr></tbody></table><p><a href="https://iterm2.com/downloads/stable/iTerm2-3_3_0.changelog">iTerm2 has supported SIXEL since its 3.3 release</a>, and recent releases continue to fix SIXEL decoding.<a href="https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-22-release/">Windows Terminal introduced support in version 1.22</a>. These version boundaries are about the terminal emulator; the module independently requires a modern PowerShell runtime.</p><h2 id="windows-terminal-readers-please-try-this" class="ps-heading">Windows Terminal readers: please try this<a class="ps-heading-anchor" href="#windows-terminal-readers-please-try-this" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>I deliberately kept the Windows claim separate from the macOS result. The renderer exists in Windows Terminal, and the module packages the Windows Skia native library, but a real end-to-end run is more valuable than an inference from two codebases.</p><p>If you have Windows Terminal 1.22 or newer and PowerShell 7.4 or newer, try:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$PSVersionTable</span>.<span style="color:#79c0ff">PSVersion</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Get-AppxPackage Microsoft.<span style="color:#79c0ff">WindowsTerminal</span> |</span></span><span style="display:flex;"><span> Select-Object Name, Version</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Install-Module -Name LibSixel.<span style="color:#79c0ff">PowerShell</span> -RequiredVersion<span style="color:#a5d6ff">'0.2.0-beta2'</span> -AllowPrerelease -Scope CurrentUser</span></span><span style="display:flex;"><span>Import-Module LibSixel.<span style="color:#79c0ff">PowerShell</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Use the inline $svg sample from the earlier section.</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$svg</span> | Out-Sixel -Width<span style="color:#a5d6ff">480</span></span></span></code></pre></td></tr></table></div></div><p>If it works, capture the terminal version, PowerShell version, architecture, and a screenshot. If it does not, the failure mode is just as useful: dependency loading, raw escape text, a blank area, or incorrect cursor placement point to different layers.</p><h2 id="limitations-worth-keeping" class="ps-heading">Limitations worth keeping<a class="ps-heading-anchor" href="#limitations-worth-keeping" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>This is an experiment, not a new universal image API for PowerShell.</p><ul><li>SIXEL palettes contain at most 256 colors.</li><li>Large images produce large terminal streams and can be slow over remote connections.</li><li>Image placement and cursor behavior vary between terminal implementations.</li><li>Multiplexers add another protocol layer and need separate testing.</li><li>SVG text depends on fonts available to Skia on the machine doing the rasterization.</li><li>The module is an experimental prerelease rather than a stable terminal graphics API.</li></ul><p>Those constraints keep the example honest, but they do not make it less fun. A generated architecture diagram, chart, QR code, or build badge appearing directly in a PowerShell session is still a delightful result from a protocol designed decades ago.</p><h2 id="takeaway" class="ps-heading">Takeaway<a class="ps-heading-anchor" href="#takeaway" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The surprising part is not that PowerShell can read an image. The surprising part is that the ordinary terminal output channel can carry the image all the way to the screen.</p><p>On macOS with iTerm2, that path works today:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>PowerShell -&gt; SIXEL -&gt; iTerm2 -&gt; pixels</span></span></code></pre></td></tr></table></div></div><p>Windows Terminal should provide the same path on Windows. If you test it, send the result. One successful screenshot—or one interesting failure—would make a useful follow-up to this little experiment.</p>
]]></content:encoded></item><item><title>Analyze Dependencies with PSQuickGraph and PSGraphView</title><link>https://powershell.org/articles/2026-08-22-analyze-dependencies-with-psquickgraph-and-psgraphview/</link><guid>https://powershell.org/articles/2026-08-22-analyze-dependencies-with-psquickgraph-and-psgraphview/</guid><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;PowerShell is excellent at collecting objects. The harder question often comes
one step later: how are those objects related?&lt;/p&gt;
&lt;p&gt;A table can tell us that an Orders API uses a database, a message broker, and a
vault. It is much less useful when we need to answer questions such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What will be affected if Azure Service Bus is unavailable?&lt;/li&gt;
&lt;li&gt;Why does the customer-facing application depend on Key Vault?&lt;/li&gt;
&lt;li&gt;In what order should the platform be deployed or migrated?&lt;/li&gt;
&lt;li&gt;Where are the cycles and tightly coupled groups when the model grows?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are graph questions. &lt;a href="https://github.com/eosfor/PSGraph"&gt;PSGraph&lt;/a&gt; provides
the graph model and algorithms through the &lt;code&gt;PSQuickGraph&lt;/code&gt; PowerShell module. The
sibling &lt;a href="https://github.com/eosfor/PSGraphView"&gt;PSGraphView&lt;/a&gt; module renders those
models as Graphviz, Vega, MSAGL, and design structure matrix views.&lt;/p&gt;</description><content:encoded>&lt;![CDATA[<p>PowerShell is excellent at collecting objects. The harder question often comes
one step later: how are those objects related?</p><p>A table can tell us that an Orders API uses a database, a message broker, and a
vault. It is much less useful when we need to answer questions such as:</p><ul><li>What will be affected if Azure Service Bus is unavailable?</li><li>Why does the customer-facing application depend on Key Vault?</li><li>In what order should the platform be deployed or migrated?</li><li>Where are the cycles and tightly coupled groups when the model grows?</li></ul><p>Those are graph questions.<a href="https://github.com/eosfor/PSGraph">PSGraph</a> provides
the graph model and algorithms through the<code>PSQuickGraph</code> PowerShell module. The
sibling<a href="https://github.com/eosfor/PSGraphView">PSGraphView</a> module renders those
models as Graphviz, Vega, MSAGL, and design structure matrix views.</p><p>This article builds one small platform model and uses it for several jobs. The
point is not the fictional architecture. The point is that the same native
PowerShell objects can support automation, analysis, and documentation without
maintaining three separate models.</p><h2 id="two-modules-with-different-jobs" class="ps-heading">Two modules with different jobs<a class="ps-heading-anchor" href="#two-modules-with-different-jobs" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The naming is worth explaining before installing anything:</p><table><thead><tr><th>Name</th><th>Responsibility</th></tr></thead><tbody><tr><td><code>PSGraph</code></td><td>The project and GitHub repository.</td></tr><tr><td><code>PSQuickGraph</code></td><td>The installable module for graph objects, algorithms, GraphML, Graphviz/DOT export, and DSM analysis.</td></tr><tr><td><code>PSGraphView</code></td><td>The installable visualization module for Graphviz, Vega, MSAGL, and DSM output.</td></tr></tbody></table><p><code>PSQuickGraph</code> is not the Microsoft Graph API, and it is not the older Graphviz
DSL module named<code>PSGraph</code>. Its focus is an object graph that can be queried and
passed through PowerShell pipelines.</p><p>The examples below use the current prerelease pair because the renderer split is
new. Pinning the versions makes the article reproducible:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Install-PSResource `</span></span><span style="display:flex;"><span> -Name PSQuickGraph `</span></span><span style="display:flex;"><span> -Version<span style="color:#a5d6ff">2.6</span>.<span style="color:#a5d6ff">0</span>-beta1 `</span></span><span style="display:flex;"><span> -Prerelease `</span></span><span style="display:flex;"><span> -Scope CurrentUser</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Install-PSResource `</span></span><span style="display:flex;"><span> -Name PSGraphView `</span></span><span style="display:flex;"><span> -Version<span style="color:#a5d6ff">0.2</span>.<span style="color:#a5d6ff">0</span>-beta1 `</span></span><span style="display:flex;"><span> -Prerelease `</span></span><span style="display:flex;"><span> -Scope CurrentUser</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Import-Module PSQuickGraph -RequiredVersion<span style="color:#a5d6ff">2.6</span>.<span style="color:#79c0ff">0</span></span></span><span style="display:flex;"><span>Import-Module PSGraphView -RequiredVersion<span style="color:#a5d6ff">0.2</span>.<span style="color:#79c0ff">0</span></span></span></code></pre></td></tr></table></div></div><p>If you only need graph construction and algorithms,<code>PSGraphView</code> is optional.</p><h2 id="model-a-platform-with-ordinary-objects" class="ps-heading">Model a platform with ordinary objects<a class="ps-heading-anchor" href="#model-a-platform-with-ordinary-objects" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The sample inventory contains applications, services, workers, shared platform
services, databases, and observability. There is no required vertex class in the
calling code; each item is a normal<code>PSCustomObject</code>:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$services</span> =<span style="color:#79c0ff">@</span>(</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Customer Portal'</span>; Kind =<span style="color:#a5d6ff">'Application'</span>; Team =<span style="color:#a5d6ff">'Experience'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Admin Portal'</span>; Kind =<span style="color:#a5d6ff">'Application'</span>; Team =<span style="color:#a5d6ff">'Operations'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Orders API'</span>; Kind =<span style="color:#a5d6ff">'Service'</span>; Team =<span style="color:#a5d6ff">'Orders'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Inventory API'</span>; Kind =<span style="color:#a5d6ff">'Service'</span>; Team =<span style="color:#a5d6ff">'Inventory'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Billing Worker'</span>; Kind =<span style="color:#a5d6ff">'Worker'</span>; Team =<span style="color:#a5d6ff">'Billing'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Notification Worker'</span>; Kind =<span style="color:#a5d6ff">'Worker'</span>; Team =<span style="color:#a5d6ff">'Experience'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Azure Service Bus'</span>; Kind =<span style="color:#a5d6ff">'Platform'</span>; Team =<span style="color:#a5d6ff">'Platform'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Orders DB'</span>; Kind =<span style="color:#a5d6ff">'Data'</span>; Team =<span style="color:#a5d6ff">'Orders'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Inventory DB'</span>; Kind =<span style="color:#a5d6ff">'Data'</span>; Team =<span style="color:#a5d6ff">'Inventory'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Billing DB'</span>; Kind =<span style="color:#a5d6ff">'Data'</span>; Team =<span style="color:#a5d6ff">'Billing'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Key Vault'</span>; Kind =<span style="color:#a5d6ff">'Platform'</span>; Team =<span style="color:#a5d6ff">'Platform'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ Name =<span style="color:#a5d6ff">'Application Insights'</span>; Kind =<span style="color:#a5d6ff">'Observability'</span>; Team =<span style="color:#a5d6ff">'Platform'</span> }</span></span><span style="display:flex;"><span>)</span></span></code></pre></td></tr></table></div></div><p>Dependencies are data too. In this model an edge points from a consumer to its
dependency:<code>Orders API -&gt; Orders DB</code> means that the API depends on the database.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">17</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">18</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">19</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">20</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">21</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">22</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">23</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$dependencies</span> =<span style="color:#79c0ff">@</span>(</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Customer Portal'</span>; To =<span style="color:#a5d6ff">'Orders API'</span>; Reason =<span style="color:#a5d6ff">'HTTPS'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Customer Portal'</span>; To =<span style="color:#a5d6ff">'Inventory API'</span>; Reason =<span style="color:#a5d6ff">'HTTPS'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Admin Portal'</span>; To =<span style="color:#a5d6ff">'Orders API'</span>; Reason =<span style="color:#a5d6ff">'HTTPS'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Admin Portal'</span>; To =<span style="color:#a5d6ff">'Inventory API'</span>; Reason =<span style="color:#a5d6ff">'HTTPS'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Orders API'</span>; To =<span style="color:#a5d6ff">'Orders DB'</span>; Reason =<span style="color:#a5d6ff">'SQL'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Orders API'</span>; To =<span style="color:#a5d6ff">'Inventory API'</span>; Reason =<span style="color:#a5d6ff">'HTTPS'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Orders API'</span>; To =<span style="color:#a5d6ff">'Azure Service Bus'</span>; Reason =<span style="color:#a5d6ff">'AMQP'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Orders API'</span>; To =<span style="color:#a5d6ff">'Key Vault'</span>; Reason =<span style="color:#a5d6ff">'Secrets'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Inventory API'</span>; To =<span style="color:#a5d6ff">'Inventory DB'</span>; Reason =<span style="color:#a5d6ff">'SQL'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Inventory API'</span>; To =<span style="color:#a5d6ff">'Key Vault'</span>; Reason =<span style="color:#a5d6ff">'Secrets'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Billing Worker'</span>; To =<span style="color:#a5d6ff">'Azure Service Bus'</span>; Reason =<span style="color:#a5d6ff">'AMQP'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Billing Worker'</span>; To =<span style="color:#a5d6ff">'Billing DB'</span>; Reason =<span style="color:#a5d6ff">'SQL'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Billing Worker'</span>; To =<span style="color:#a5d6ff">'Key Vault'</span>; Reason =<span style="color:#a5d6ff">'Secrets'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Notification Worker'</span>; To =<span style="color:#a5d6ff">'Azure Service Bus'</span>; Reason =<span style="color:#a5d6ff">'AMQP'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Notification Worker'</span>; To =<span style="color:#a5d6ff">'Key Vault'</span>; Reason =<span style="color:#a5d6ff">'Secrets'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Customer Portal'</span>; To =<span style="color:#a5d6ff">'Application Insights'</span>; Reason =<span style="color:#a5d6ff">'Telemetry'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Admin Portal'</span>; To =<span style="color:#a5d6ff">'Application Insights'</span>; Reason =<span style="color:#a5d6ff">'Telemetry'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Orders API'</span>; To =<span style="color:#a5d6ff">'Application Insights'</span>; Reason =<span style="color:#a5d6ff">'Telemetry'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Inventory API'</span>; To =<span style="color:#a5d6ff">'Application Insights'</span>; Reason =<span style="color:#a5d6ff">'Telemetry'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Billing Worker'</span>; To =<span style="color:#a5d6ff">'Application Insights'</span>; Reason =<span style="color:#a5d6ff">'Telemetry'</span> }</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">pscustomobject</span>]<span style="color:#79c0ff">@</span>{ From =<span style="color:#a5d6ff">'Notification Worker'</span>; To =<span style="color:#a5d6ff">'Application Insights'</span>; Reason =<span style="color:#a5d6ff">'Telemetry'</span> }</span></span><span style="display:flex;"><span>)</span></span></code></pre></td></tr></table></div></div><p>Build the graph in two passes. Explicitly adding vertices preserves isolated
services; adding only edges would omit objects that currently have no
relationships.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">17</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">18</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$serviceByName</span> =<span style="color:#79c0ff">@</span>{}</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$graph</span> = New-Graph</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$service</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$services</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$serviceByName</span>[<span style="color:#79c0ff">$service</span>.Name] =<span style="color:#79c0ff">$service</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$vertex</span> = Add-Vertex -Graph<span style="color:#79c0ff">$graph</span> -Vertex<span style="color:#79c0ff">$service</span> -PassThru</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$vertex</span>.<span style="color:#79c0ff">Metadata</span> |</span></span><span style="display:flex;"><span> Add-Member -NotePropertyName Team -NotePropertyValue<span style="color:#79c0ff">$service</span>.<span style="color:#79c0ff">Team</span></span></span><span style="display:flex;"><span>}</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$dependency</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$dependencies</span>) {</span></span><span style="display:flex;"><span> Add-Edge `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$graph</span> `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$serviceByName</span>[<span style="color:#79c0ff">$dependency</span>.From] `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$serviceByName</span>[<span style="color:#79c0ff">$dependency</span>.To] `</span></span><span style="display:flex;"><span> -Tag<span style="color:#79c0ff">$dependency</span>.<span style="color:#79c0ff">Reason</span> |</span></span><span style="display:flex;"><span> Out-Null</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p>The sample graph contains 12 vertices and 21 directed edges. The wrapper vertex
retains the original object in<code>OriginalObject</code>, so analysis results can return
to normal PowerShell processing at any time.</p><h2 id="first-view-the-dependency-map" class="ps-heading">First view: the dependency map<a class="ps-heading-anchor" href="#first-view-the-dependency-map" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p><code>PSQuickGraph</code> exports the model as DOT;<code>PSGraphView</code> asks Graphviz to lay it out
and return SVG. Keeping these steps separate is useful: algorithms can run on a
server that never renders an image, while a documentation build can apply its
own visual style.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">17</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$dot</span> = Export-Graph `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$graph</span> `</span></span><span style="display:flex;"><span> -Format Graphviz `</span></span><span style="display:flex;"><span> -GraphScript {<span style="color:#79c0ff">@</span>{ rankdir =<span style="color:#a5d6ff">'LR'</span>; bgcolor =<span style="color:#a5d6ff">'white'</span> } } `</span></span><span style="display:flex;"><span> -VertexScript {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">@</span>{</span></span><span style="display:flex;"><span> shape =<span style="color:#a5d6ff">'Record'</span></span></span><span style="display:flex;"><span> style =<span style="color:#a5d6ff">'filled'</span></span></span><span style="display:flex;"><span> label =<span style="color:#a5d6ff">"{{ {0} | {1} }}"</span><span style="color:#ff7b72;font-weight:bold">-f</span><span style="color:#79c0ff">$_</span>.Name,<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">Kind</span></span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Export-GraphvizView `</span></span><span style="display:flex;"><span> -InputObject<span style="color:#79c0ff">$dot</span> `</span></span><span style="display:flex;"><span> -Renderer Dot `</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">-As</span> Svg `</span></span><span style="display:flex;"><span> -OutputPath ./platform-dependencies.<span style="color:#79c0ff">svg</span></span></span></code></pre></td></tr></table></div></div><p><img src="/images/articles/psgraphview-platform-dependencies.svg" alt="A consumer-to-dependency graph of the sample platform. Each record shows the component name and kind."/><p>A drawing is already useful for a design review, but the graph becomes more
valuable when it answers operational questions.</p><h2 id="immediate-dependencies-and-dependents" class="ps-heading">Immediate dependencies and dependents<a class="ps-heading-anchor" href="#immediate-dependencies-and-dependents" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Because the edge direction is explicit, outgoing and incoming edges answer two
different questions:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">9</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$orders</span> =<span style="color:#79c0ff">$graph</span>.<span style="color:#79c0ff">Vertices</span> | Where-Object Label<span style="color:#ff7b72;font-weight:bold">-EQ</span><span style="color:#a5d6ff">'Orders API'</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># What does Orders API require?</span></span></span><span style="display:flex;"><span>Get-OutEdge -Graph<span style="color:#79c0ff">$graph</span> -Vertex<span style="color:#79c0ff">$orders</span> |</span></span><span style="display:flex;"><span> ForEach-Object {<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">Target</span>.<span style="color:#79c0ff">OriginalObject</span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># What calls Orders API directly?</span></span></span><span style="display:flex;"><span>Get-InEdge -Graph<span style="color:#79c0ff">$graph</span> -Vertex<span style="color:#79c0ff">$orders</span> |</span></span><span style="display:flex;"><span> ForEach-Object {<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">Source</span>.<span style="color:#79c0ff">OriginalObject</span> }</span></span></code></pre></td></tr></table></div></div><p>The result is still the inventory object, not display text. It can be grouped by
team, joined with ownership data, exported to CSV, or used to open incidents.</p><h2 id="explain-a-dependency-with-a-path" class="ps-heading">Explain a dependency with a path<a class="ps-heading-anchor" href="#explain-a-dependency-with-a-path" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Knowing that two components are connected is not always enough.<code>Get-GraphPath</code>
returns the edge sequence that explains the relationship:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$from</span> =<span style="color:#79c0ff">$graph</span>.<span style="color:#79c0ff">Vertices</span> | Where-Object Label<span style="color:#ff7b72;font-weight:bold">-EQ</span><span style="color:#a5d6ff">'Customer Portal'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$to</span> =<span style="color:#79c0ff">$graph</span>.<span style="color:#79c0ff">Vertices</span> | Where-Object Label<span style="color:#ff7b72;font-weight:bold">-EQ</span><span style="color:#a5d6ff">'Key Vault'</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$path</span> = Get-GraphPath -Graph<span style="color:#79c0ff">$graph</span> -From<span style="color:#79c0ff">$from</span> -To<span style="color:#79c0ff">$to</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">@</span>(<span style="color:#79c0ff">$path</span>.<span style="color:#79c0ff">Source</span>.Label) +<span style="color:#79c0ff">$path</span>[<span style="color:#a5d6ff">-1</span>].<span style="color:#79c0ff">Target</span>.<span style="color:#79c0ff">Label</span></span></span></code></pre></td></tr></table></div></div><p>For this model the result is:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Customer Portal -&gt; Orders API -&gt; Key Vault</span></span></code></pre></td></tr></table></div></div><p>This is useful in change reviews and incident response because it provides an
explanation, not just a Boolean answer.</p><h2 id="calculate-blast-radius" class="ps-heading">Calculate blast radius<a class="ps-heading-anchor" href="#calculate-blast-radius" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Suppose Azure Service Bus is unavailable. Every vertex that can reach it through
consumer-to-dependency edges is transitively affected:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$serviceBus</span> =<span style="color:#79c0ff">$graph</span>.<span style="color:#79c0ff">Vertices</span> |</span></span><span style="display:flex;"><span> Where-Object Label<span style="color:#ff7b72;font-weight:bold">-EQ</span><span style="color:#a5d6ff">'Azure Service Bus'</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$affected</span> =<span style="color:#79c0ff">$graph</span>.<span style="color:#79c0ff">Vertices</span> |</span></span><span style="display:flex;"><span> Where-Object Label<span style="color:#ff7b72;font-weight:bold">-NE</span><span style="color:#79c0ff">$serviceBus</span>.<span style="color:#79c0ff">Label</span> |</span></span><span style="display:flex;"><span> Where-Object {</span></span><span style="display:flex;"><span> Test-GraphPath -Graph<span style="color:#79c0ff">$graph</span> -From<span style="color:#79c0ff">$_</span> -To<span style="color:#79c0ff">$serviceBus</span></span></span><span style="display:flex;"><span> } |</span></span><span style="display:flex;"><span> Sort-Object Label</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$affected</span>.<span style="color:#79c0ff">OriginalObject</span></span></span></code></pre></td></tr></table></div></div><p><img src="/images/articles/psgraphview-service-bus-impact.svg" alt="Azure Service Bus and every direct or transitive consumer are marked with a bold border and a warning marker."/><p>The result includes both direct consumers and applications affected indirectly:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Admin Portal</span></span><span style="display:flex;"><span>Billing Worker</span></span><span style="display:flex;"><span>Customer Portal</span></span><span style="display:flex;"><span>Notification Worker</span></span><span style="display:flex;"><span>Orders API</span></span></code></pre></td></tr></table></div></div><p><code>Get-InEdge</code> finds immediate consumers.<code>Test-GraphPath</code> also finds portals that
depend on Service Bus indirectly through Orders API.</p><h2 id="derive-a-deployment-order" class="ps-heading">Derive a deployment order<a class="ps-heading-anchor" href="#derive-a-deployment-order" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The same graph can become an execution plan. With consumer-to-dependency edges,
reversing the topological order puts dependencies before their consumers:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">7</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$deploymentOrder</span> = Get-GraphTopologicalSort `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$graph</span> `</span></span><span style="display:flex;"><span> -Reverse</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$deploymentOrder</span> |</span></span><span style="display:flex;"><span> Select-Object -ExpandProperty OriginalObject |</span></span><span style="display:flex;"><span> Select-Object Name, Kind, Team</span></span></code></pre></td></tr></table></div></div><p>The result starts with shared dependencies and ends with applications:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Azure Service Bus</span></span><span style="display:flex;"><span>Orders DB</span></span><span style="display:flex;"><span>Key Vault</span></span><span style="display:flex;"><span>Inventory DB</span></span><span style="display:flex;"><span>Billing DB</span></span><span style="display:flex;"><span>Application Insights</span></span><span style="display:flex;"><span>Inventory API</span></span><span style="display:flex;"><span>Billing Worker</span></span><span style="display:flex;"><span>Orders API</span></span><span style="display:flex;"><span>Notification Worker</span></span><span style="display:flex;"><span>Customer Portal</span></span><span style="display:flex;"><span>Admin Portal</span></span></code></pre></td></tr></table></div></div><p>Topological sorting is appropriate only for a directed acyclic graph. If two
services depend on each other, the sort fails rather than inventing a safe
order. That failure is useful evidence: the cycle needs an explicit migration
strategy or an architectural change.</p><h2 id="when-a-node-link-diagram-becomes-too-dense" class="ps-heading">When a node-link diagram becomes too dense<a class="ps-heading-anchor" href="#when-a-node-link-diagram-becomes-too-dense" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Arrows work well for a dozen components. They become a hairball for a hundred. A
design structure matrix (DSM) represents the same edges as cells: the row is the
consumer and the column is its dependency.</p><p><code>PSQuickGraph</code> creates and sequences the matrix;<code>PSGraphView</code> renders it:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$dsm</span> = New-DSM -Graph<span style="color:#79c0ff">$graph</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$sequencedDsm</span> = Start-DSMSequencing `</span></span><span style="display:flex;"><span> -Dsm<span style="color:#79c0ff">$dsm</span> `</span></span><span style="display:flex;"><span> -LoopDetectionMethod Condensation</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Export-DSMView `</span></span><span style="display:flex;"><span> -SequencedDsm<span style="color:#79c0ff">$sequencedDsm</span> `</span></span><span style="display:flex;"><span> -Renderer DsmVegaMatrix `</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">-As</span> Json `</span></span><span style="display:flex;"><span> -Path ./dependency-matrix.<span style="color:#79c0ff">json</span></span></span></code></pre></td></tr></table></div></div><p><img src="/images/articles/psgraphview-dependency-matrix.svg" alt="A sequenced design structure matrix of the same platform. Filled cells map consumers to dependencies without crossing edges."/><p>The Vega renderer produces an interactive matrix whose row and column labels can
be highlighted on hover. The static image above uses the same Vega specification
for the article page. For larger models,<code>Start-DSMClustering</code> can group strongly
related components before rendering. That makes the matrix useful for finding
candidate service boundaries, not merely documenting the current state.</p><h2 id="other-scenarios-for-the-same-pattern" class="ps-heading">Other scenarios for the same pattern<a class="ps-heading-anchor" href="#other-scenarios-for-the-same-pattern" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Only the data collection step changes between domains. The graph workflow
remains: collect objects, create stable vertices, add directed relationships,
ask questions, then choose a view.</p><ul><li><strong>Security events:</strong> connect processes, users, hosts, files, and network
destinations to reconstruct a suspicious chain.</li><li><strong>Network policy:</strong> turn accepted and rejected firewall flows into host and
port relationships.</li><li><strong>Infrastructure as code:</strong> model semantic Bicep dependencies and validate
cross-resource relationships, as shown in<a href="/articles/2026-07-24-validate-azure-resource-relationships-with-psrule-and-powershell-graphs/">Validate Azure Resource Relationships with PSRule and PowerShell Graphs</a>.</li><li><strong>Web diagnostics:</strong> connect pages to scripts, APIs, and third-party origins
discovered through Chrome DevTools Protocol.</li><li><strong>Execution graphs:</strong> use topological order to evaluate a computation graph,
as shown in<a href="/articles/2026-07-24-explore-micrograd-with-verso-and-powershell/">Explore Micrograd with Verso and PowerShell</a>.</li></ul><p>These examples look different on screen, but the useful questions are the same:
what depends on this, how did we get there, what order is valid, and where is the
system too tightly coupled?</p><h2 id="takeaways" class="ps-heading">Takeaways<a class="ps-heading-anchor" href="#takeaways" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p><code>PSQuickGraph</code> is most useful when a diagram is not the final product. The graph
can drive impact reports, validation, deployment ordering, and incident
analysis.<code>PSGraphView</code> then turns that same tested model into the representation
that fits the audience: a familiar node-link diagram, an interactive view, or a
dense DSM.</p><p>The practical pattern is small:</p><ol><li>Keep vertices as domain objects with stable names or IDs.</li><li>Decide and document the edge direction.</li><li>Use algorithms before reaching for visualization.</li><li>Render from the same model instead of maintaining diagrams by hand.</li></ol><p>Once relationships become first-class data, PowerShell can do much more than
draw boxes and arrows.</p>
]]></content:encoded></item><item><title>Explore Micrograd with Verso and PowerShell</title><link>https://powershell.org/articles/2026-07-24-explore-micrograd-with-verso-and-powershell/</link><guid>https://powershell.org/articles/2026-07-24-explore-micrograd-with-verso-and-powershell/</guid><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;&lt;a href="https://github.com/DataficationSDK/Verso"&gt;Verso&lt;/a&gt; is an open-source interactive
notebook platform and embeddable .NET execution engine. Its language kernels include
PowerShell, C#, F#, Python, SQL, JavaScript, TypeScript, and HTTP, and it provides
VS Code and browser front ends.&lt;/p&gt;
&lt;p&gt;That timing is useful for PowerShell users. The
&lt;a href="https://github.com/dotnet/interactive"&gt;.NET Interactive repository&lt;/a&gt; was archived in
April 2026, leaving a gap for maintained multi-language .NET notebooks. Verso is an
actively developed option with persistent kernel state, rich output, cross-language
variable sharing, and headless notebook execution.&lt;/p&gt;</description><content:encoded>&lt;![CDATA[<p><a href="https://github.com/DataficationSDK/Verso">Verso</a> is an open-source interactive
notebook platform and embeddable .NET execution engine. Its language kernels include
PowerShell, C#, F#, Python, SQL, JavaScript, TypeScript, and HTTP, and it provides
VS Code and browser front ends.</p><p>That timing is useful for PowerShell users. The<a href="https://github.com/dotnet/interactive">.NET Interactive repository</a> was archived in
April 2026, leaving a gap for maintained multi-language .NET notebooks. Verso is an
actively developed option with persistent kernel state, rich output, cross-language
variable sharing, and headless notebook execution.</p><p>This post walks through an<a href="https://github.com/DataficationSDK/Verso/blob/3f8629154a28824ad5fbd0eaca49c3ef57168704/samples/Notebooks/powershell/micrograd/micrograd-ps.verso">experimental PowerShell micrograd notebook</a>
built for Verso. The sample was proposed separately and is not currently part of
Verso&rsquo;s<code>main</code> branch, so treat it as an exploration rather than a shipped Verso
sample.</p><p>The notebook ports the core ideas from Andrej Karpathy&rsquo;s<a href="https://github.com/karpathy/micrograd">micrograd</a> to PowerShell. The original
project is intentionally tiny: scalar-valued reverse-mode automatic differentiation,
then a small neural-network library on top. Karpathy&rsquo;s video,<a href="https://youtu.be/VMj-3S1tku0">The spelled-out intro to neural networks and backpropagation: building micrograd</a>,
is effective because it does not hide the graph. The PowerShell version keeps that
spirit, using<a href="https://github.com/eosfor/PSGraph">PSQuickGraph</a> and<a href="https://github.com/eosfor/PSGraphView/tree/feature/direct-graphviz-integration">PSGraphView</a>
to render the computation graph directly from objects created in the notebook.</p><p>The diagrams in this article were generated by PowerShell from the same helper
scripts used by the notebook and exported as Graphviz SVG through PSGraphView.</p><h2 id="notebook-setup" class="ps-heading">Notebook Setup<a class="ps-heading-anchor" href="#notebook-setup" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Install the Verso CLI and the two graph modules:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>dotnet tool install --global Verso.Cli</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Install-Module -Name PSQuickGraph -RequiredVersion<span style="color:#a5d6ff">2.5</span>.<span style="color:#79c0ff">0</span> -Scope CurrentUser</span></span><span style="display:flex;"><span>Install-Module -Name PSGraphView -RequiredVersion<span style="color:#a5d6ff">0.1</span>.<span style="color:#79c0ff">0</span> -Scope CurrentUser</span></span></code></pre></td></tr></table></div></div><p>To open the exact experimental notebook used in this article, check out its commit
and pass the notebook path to Verso:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>git clone https<span style="color:#f85149">:</span>//github.com/DataficationSDK/Verso.<span style="color:#79c0ff">git</span></span></span><span style="display:flex;"><span>Set-Location ./Verso</span></span><span style="display:flex;"><span>git checkout 3f8629154a28824ad5fbd0eaca49c3ef57168704</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>verso serve ./samples/Notebooks/powershell/micrograd/micrograd-ps.<span style="color:#79c0ff">verso</span></span></span></code></pre></td></tr></table></div></div><p>The notebook starts with the normal module path:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Import-Module PSQuickGraph</span></span><span style="display:flex;"><span>Import-Module PSGraphView</span></span></code></pre></td></tr></table></div></div><p>The implementation is split into four scripts:</p><ul><li><code>value.ps1</code> defines the scalar<code>Value</code> class and operator overloads.</li><li><code>graphHelper.ps1</code> converts<code>Value</code> objects into graph vertices and renders them.</li><li><code>neuronHelper.ps1</code> defines<code>Neuron</code>,<code>Layer</code>, and<code>MLP</code>.</li><li><code>helpers.ps1</code> contains<code>Zip</code> and<code>Sum-Value</code>, small utilities used when building the loss.</li></ul><p>The notebook loads them directly:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>. ./value.<span style="color:#79c0ff">ps1</span></span></span><span style="display:flex;"><span>. ./graphHelper.<span style="color:#79c0ff">ps1</span></span></span><span style="display:flex;"><span>. ./neuronHelper.<span style="color:#79c0ff">ps1</span></span></span><span style="display:flex;"><span>. ./helpers.<span style="color:#79c0ff">ps1</span></span></span></code></pre></td></tr></table></div></div><p>The key class is<code>Value</code>. Each instance stores<code>data</code>,<code>grad</code>, a<code>label</code>, the operation that produced it, the child values that fed into that operation, and a<code>backward</code> closure. That is the entire trick: normal arithmetic produces both a result and a tiny piece of local derivative logic.</p><p>For addition, the derivative is one for both inputs:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>static [<span style="color:#79c0ff;font-weight:bold">Value</span>] op_Addition([<span style="color:#79c0ff;font-weight:bold">Value</span>]<span style="color:#79c0ff">$left</span>, [<span style="color:#79c0ff;font-weight:bold">Value</span>]<span style="color:#79c0ff">$right</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$out</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#79c0ff">$left</span>.<span style="color:#79c0ff">data</span> +<span style="color:#79c0ff">$right</span>.data,<span style="color:#79c0ff">@</span>(<span style="color:#79c0ff">$left</span>,<span style="color:#79c0ff">$right</span>),<span style="color:#a5d6ff">"+"</span>,<span style="color:#a5d6ff">"+_res"</span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">backward</span> = {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$left</span>.<span style="color:#79c0ff">grad</span> +=<span style="color:#a5d6ff">1</span> *<span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">grad</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$right</span>.<span style="color:#79c0ff">grad</span> +=<span style="color:#a5d6ff">1</span> *<span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">grad</span></span></span><span style="display:flex;"><span> }.GetNewClosure()</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span><span style="color:#79c0ff">$out</span></span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p>For multiplication, each input receives the other input&rsquo;s data multiplied by the output gradient:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>static [<span style="color:#79c0ff;font-weight:bold">Value</span>] op_Multiply([<span style="color:#79c0ff;font-weight:bold">Value</span>]<span style="color:#79c0ff">$left</span>, [<span style="color:#79c0ff;font-weight:bold">Value</span>]<span style="color:#79c0ff">$right</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$out</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#79c0ff">$left</span>.<span style="color:#79c0ff">data</span> *<span style="color:#79c0ff">$right</span>.data,<span style="color:#79c0ff">@</span>(<span style="color:#79c0ff">$left</span>,<span style="color:#79c0ff">$right</span>),<span style="color:#a5d6ff">"*"</span>,<span style="color:#a5d6ff">"*_res"</span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">backward</span> = {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$left</span>.<span style="color:#79c0ff">grad</span> +=<span style="color:#79c0ff">$right</span>.<span style="color:#79c0ff">data</span> *<span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">grad</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$right</span>.<span style="color:#79c0ff">grad</span> +=<span style="color:#79c0ff">$left</span>.<span style="color:#79c0ff">data</span> *<span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">grad</span></span></span><span style="display:flex;"><span> }.GetNewClosure()</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span><span style="color:#79c0ff">$out</span></span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p><code>Tanh()</code> follows the same pattern, but the derivative is<code>1 - tanh(x)^2</code>:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>[<span style="color:#79c0ff;font-weight:bold">Value</span>] Tanh(){</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$v</span> =<span style="color:#79c0ff">$this</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$t</span> = [<span style="color:#79c0ff;font-weight:bold">Math</span>]::Tanh(<span style="color:#79c0ff">$this</span>.data)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$out</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#79c0ff">$t</span>,<span style="color:#79c0ff">@</span>(<span style="color:#79c0ff">$this</span>),<span style="color:#a5d6ff">"tanh"</span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">backward</span> = {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$v</span>.<span style="color:#79c0ff">grad</span> += (<span style="color:#a5d6ff">1</span> - [<span style="color:#79c0ff;font-weight:bold">Math</span>]::Pow(<span style="color:#79c0ff">$t</span>,<span style="color:#a5d6ff">2</span>)) *<span style="color:#79c0ff">$out</span>.<span style="color:#79c0ff">grad</span></span></span><span style="display:flex;"><span> }.GetNewClosure()</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span><span style="color:#79c0ff">$out</span></span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><h2 id="scalar-computation-graph" class="ps-heading">Scalar Computation Graph<a class="ps-heading-anchor" href="#scalar-computation-graph" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The first notebook example is the same kind of scalar expression Karpathy uses to make backpropagation visible:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">7</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$a</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">2.0</span>,<span style="color:#a5d6ff">'a'</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$b</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-3.0</span>,<span style="color:#a5d6ff">'b'</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$c</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">10.0</span>,<span style="color:#a5d6ff">'c'</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$e</span> =<span style="color:#79c0ff">$a</span> *<span style="color:#79c0ff">$b</span>;<span style="color:#79c0ff">$e</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'e'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$d</span> =<span style="color:#79c0ff">$e</span> +<span style="color:#79c0ff">$c</span>;<span style="color:#79c0ff">$d</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'d'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$f</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-2.0</span>,<span style="color:#a5d6ff">'f'</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$L</span> =<span style="color:#79c0ff">$d</span> *<span style="color:#79c0ff">$f</span>;<span style="color:#79c0ff">$L</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'L'</span></span></span></code></pre></td></tr></table></div></div><p>At this point<code>$L.data</code> is<code>-8</code>, and all gradients are still zero. The graph is created from the output value:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$scalarGraph</span> = New-ExpressionGraph -val<span style="color:#79c0ff">$L</span></span></span><span style="display:flex;"><span>Show-ExpressionGraph -Graph<span style="color:#79c0ff">$scalarGraph</span></span></span></code></pre></td></tr></table></div></div><p><img src="/images/articles/verso-micrograd-scalar-before.svg" alt="Scalar expression before backpropagation; data values are populated and every gradient is zero"/><p><code>New-ExpressionGraph</code> walks from the output node back through<code>children</code>. It creates record-shaped nodes for values and ellipse-shaped nodes for operations. Because<code>Value</code> objects are actual object references, helper hashtables prevent duplicate vertices when a value is reached more than once.</p><h2 id="backpropagation-order" class="ps-heading">Backpropagation Order<a class="ps-heading-anchor" href="#backpropagation-order" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Backpropagation is not run over the display graph. The notebook builds a second graph directly on the original<code>Value</code> objects:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$bpGraph</span> = New-BackpropagationGraph -val<span style="color:#79c0ff">$L</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$L</span>.<span style="color:#79c0ff">grad</span> =<span style="color:#a5d6ff">1.0</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Get-GraphTopologicalSort -Graph<span style="color:#79c0ff">$bpGraph</span> -Reverse |</span></span><span style="display:flex;"><span> ForEach-Object {<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">OriginalObject</span> } |</span></span><span style="display:flex;"><span> ForEach-Object { &amp;<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">backward</span> }</span></span></code></pre></td></tr></table></div></div><p>The output gradient starts at<code>1.0</code>, because<code>dL/dL = 1</code>. Then<code>Get-GraphTopologicalSort -Reverse</code> visits the output first and walks backward toward the leaves. Each node executes the closure captured when the value was created. After the pass, the visualization graph is rebuilt so the display nodes get a fresh snapshot of<code>grad</code>.</p><p><img src="/images/articles/verso-micrograd-scalar-after.svg" alt="The scalar expression after backpropagation; gradients show how each input changes L"/><p>This is the important implementation detail: the graph is not just a drawing. It is the execution dependency structure for reverse-mode autodiff.</p><h2 id="one-neuron" class="ps-heading">One Neuron<a class="ps-heading-anchor" href="#one-neuron" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The next cell builds a tiny neuron by hand: two inputs, two weights, a bias, and a<code>tanh</code> activation.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$x1</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">2.0</span>,<span style="color:#a5d6ff">'x1'</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$x2</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">0.0</span>,<span style="color:#a5d6ff">'x2'</span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$w1</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-3.0</span>,<span style="color:#a5d6ff">'w1'</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$w2</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">1.0</span>,<span style="color:#a5d6ff">'w2'</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$b</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">6.8813735870195432</span>,<span style="color:#a5d6ff">'b'</span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$x1w1</span> =<span style="color:#79c0ff">$x1</span> *<span style="color:#79c0ff">$w1</span>;<span style="color:#79c0ff">$x1w1</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'x1*w1'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$x2w2</span> =<span style="color:#79c0ff">$x2</span> *<span style="color:#79c0ff">$w2</span>;<span style="color:#79c0ff">$x2w2</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'x2*w2'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$x1w1x2w2</span> =<span style="color:#79c0ff">$x1w1</span> +<span style="color:#79c0ff">$x2w2</span>;<span style="color:#79c0ff">$x1w1x2w2</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'x1*w1 + x2*w2'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$n</span> =<span style="color:#79c0ff">$x1w1x2w2</span> +<span style="color:#79c0ff">$b</span>;<span style="color:#79c0ff">$n</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'n'</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$o</span> =<span style="color:#79c0ff">$n</span>.<span style="color:#79c0ff">Tanh</span>();<span style="color:#79c0ff">$o</span>.<span style="color:#79c0ff">label</span> =<span style="color:#a5d6ff">'o'</span></span></span></code></pre></td></tr></table></div></div><p><img src="/images/articles/verso-micrograd-neuron-before.svg" alt="A single tanh neuron before the backward pass"/><p>Running the same topological backward pass from<code>$o</code> fills the gradients for the input, weights, bias, and intermediate values:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$bpNeuronGraph</span> = New-BackpropagationGraph -val<span style="color:#79c0ff">$o</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$o</span>.<span style="color:#79c0ff">grad</span> =<span style="color:#a5d6ff">1.0</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Get-GraphTopologicalSort -Graph<span style="color:#79c0ff">$bpNeuronGraph</span> -Reverse |</span></span><span style="display:flex;"><span> ForEach-Object {<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">OriginalObject</span> } |</span></span><span style="display:flex;"><span> ForEach-Object { &amp;<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">backward</span> }</span></span></code></pre></td></tr></table></div></div><p><img src="/images/articles/verso-micrograd-neuron-after.svg" alt="The neuron after the tanh derivative has propagated through additions and multiplications"/><p>This is where the notebook starts to feel useful as a teaching tool. You can inspect every scalar contribution to the neuron instead of treating the neuron as a black box.</p><h2 id="layer-and-mlp" class="ps-heading">Layer and MLP<a class="ps-heading-anchor" href="#layer-and-mlp" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>After the manual neuron,<code>neuronHelper.ps1</code> turns the same logic into classes. A<code>Neuron</code> owns an array of weights and a bias:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">17</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">18</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">19</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">20</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">21</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#ff7b72">class</span><span style="color:#6e7681"/>Neuron {</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value[]</span>]<span style="color:#79c0ff">$w</span></span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]<span style="color:#79c0ff">$b</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span> Neuron([<span style="color:#79c0ff;font-weight:bold">int</span>]<span style="color:#79c0ff">$nin</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$this</span>.<span style="color:#79c0ff">w</span> =<span style="color:#ff7b72">for</span> (<span style="color:#79c0ff">$i</span> =<span style="color:#a5d6ff">0</span>;<span style="color:#79c0ff">$i</span><span style="color:#ff7b72;font-weight:bold">-lt</span><span style="color:#79c0ff">$nin</span>;<span style="color:#79c0ff">$i</span>++) {</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(([<span style="color:#79c0ff;font-weight:bold">Random</span>]::Shared.<span style="color:#79c0ff">NextDouble</span>() *<span style="color:#a5d6ff">2</span> -<span style="color:#a5d6ff">1</span>),<span style="color:#a5d6ff">"w</span><span style="color:#79c0ff">$i</span><span style="color:#a5d6ff">"</span>)</span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$this</span>.<span style="color:#79c0ff">b</span> = [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(([<span style="color:#79c0ff;font-weight:bold">Random</span>]::Shared.<span style="color:#79c0ff">NextDouble</span>() *<span style="color:#a5d6ff">2</span> -<span style="color:#a5d6ff">1</span>),<span style="color:#a5d6ff">"b"</span>)</span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>] Invoke([<span style="color:#79c0ff;font-weight:bold">Value[]</span>]<span style="color:#79c0ff">$x</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$sum</span> =<span style="color:#79c0ff">$this</span>.<span style="color:#79c0ff">b</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (<span style="color:#79c0ff">$i</span> =<span style="color:#a5d6ff">0</span>;<span style="color:#79c0ff">$i</span><span style="color:#ff7b72;font-weight:bold">-lt</span><span style="color:#79c0ff">$this</span>.<span style="color:#79c0ff">w</span>.Count;<span style="color:#79c0ff">$i</span>++) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$sum</span> =<span style="color:#79c0ff">$sum</span> + (<span style="color:#79c0ff">$this</span>.w[<span style="color:#79c0ff">$i</span>] *<span style="color:#79c0ff">$x</span>[<span style="color:#79c0ff">$i</span>])</span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span><span style="color:#79c0ff">$sum</span>.<span style="color:#79c0ff">Tanh</span>()</span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p>A<code>Layer</code> applies several neurons to the same input vector. An<code>MLP</code> chains layers so each layer receives the output vector from the previous layer:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$x</span> =<span style="color:#79c0ff">@</span>(</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">2.0</span>,<span style="color:#a5d6ff">'x1'</span>)</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">3.0</span>,<span style="color:#a5d6ff">'x2'</span>)</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-1.0</span>,<span style="color:#a5d6ff">'x3'</span>)</span></span><span style="display:flex;"><span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$layer</span> = [<span style="color:#79c0ff;font-weight:bold">Layer</span>]::new(<span style="color:#a5d6ff">3</span>,<span style="color:#a5d6ff">4</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$layer</span>.<span style="color:#79c0ff">Invoke</span>(<span style="color:#79c0ff">$x</span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$net</span> = [<span style="color:#79c0ff;font-weight:bold">MLP</span>]::new(<span style="color:#a5d6ff">3</span>,<span style="color:#79c0ff">@</span>(<span style="color:#a5d6ff">4</span>,<span style="color:#a5d6ff">4</span>,<span style="color:#a5d6ff">1</span>))</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$res</span> =<span style="color:#79c0ff">$net</span>.<span style="color:#79c0ff">Invoke</span>(<span style="color:#79c0ff">$x</span>)</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$res</span></span></span></code></pre></td></tr></table></div></div><p>The notebook can render the full MLP expression graph too:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$netGraph</span> = New-ExpressionGraph -val<span style="color:#79c0ff">$res</span>[<span style="color:#a5d6ff">0</span>]</span></span><span style="display:flex;"><span>Show-ExpressionGraph -Graph<span style="color:#79c0ff">$netGraph</span> -rankdir<span style="color:#a5d6ff">'TD'</span></span></span></code></pre></td></tr></table></div></div><p>That graph is intentionally not embedded here: it is already wide enough to be less readable in a blog post. The smaller scalar and neuron graphs make the mechanics clearer.</p><h2 id="training-data-and-loss" class="ps-heading">Training Data and Loss<a class="ps-heading-anchor" href="#training-data-and-loss" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The training set is the small toy dataset from the micrograd walkthrough:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$xs</span> =<span style="color:#79c0ff">@</span>(</span></span><span style="display:flex;"><span><span style="color:#79c0ff">@</span>([<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">2.0</span>,<span style="color:#a5d6ff">'x11'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">3.0</span>,<span style="color:#a5d6ff">'x12'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-1.0</span>,<span style="color:#a5d6ff">'x13'</span>)),</span></span><span style="display:flex;"><span><span style="color:#79c0ff">@</span>([<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">3.0</span>,<span style="color:#a5d6ff">'x21'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-1.0</span>,<span style="color:#a5d6ff">'x22'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">0.5</span>,<span style="color:#a5d6ff">'x23'</span>)),</span></span><span style="display:flex;"><span><span style="color:#79c0ff">@</span>([<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">0.5</span>,<span style="color:#a5d6ff">'x31'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">1.0</span>,<span style="color:#a5d6ff">'x32'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">1.0</span>,<span style="color:#a5d6ff">'x33'</span>)),</span></span><span style="display:flex;"><span><span style="color:#79c0ff">@</span>([<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">1.0</span>,<span style="color:#a5d6ff">'x41'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">1.0</span>,<span style="color:#a5d6ff">'x42'</span>), [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-1.0</span>,<span style="color:#a5d6ff">'x43'</span>))</span></span><span style="display:flex;"><span>)</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$ys</span> =<span style="color:#79c0ff">@</span>(</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">1.0</span>,<span style="color:#a5d6ff">'y1'</span>),</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-1.0</span>,<span style="color:#a5d6ff">'y2'</span>),</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">-1.0</span>,<span style="color:#a5d6ff">'y3'</span>),</span></span><span style="display:flex;"><span> [<span style="color:#79c0ff;font-weight:bold">Value</span>]::new(<span style="color:#a5d6ff">1.0</span>,<span style="color:#a5d6ff">'y4'</span>)</span></span><span style="display:flex;"><span>)</span></span></code></pre></td></tr></table></div></div><p>The loss is sum of squared errors:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">7</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$net</span> = [<span style="color:#79c0ff;font-weight:bold">MLP</span>]::new(<span style="color:#a5d6ff">3</span>,<span style="color:#79c0ff">@</span>(<span style="color:#a5d6ff">4</span>,<span style="color:#a5d6ff">4</span>,<span style="color:#a5d6ff">1</span>))</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$ypred</span> =<span style="color:#79c0ff">$xs</span> | ForEach-Object {<span style="color:#79c0ff">$net</span>.<span style="color:#79c0ff">Invoke</span>(<span style="color:#79c0ff">$_</span>)[<span style="color:#a5d6ff">0</span>] }</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$loss</span> = Zip -Left<span style="color:#79c0ff">$ys</span> -Right<span style="color:#79c0ff">$ypred</span> | Sum-Value {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$diff</span> =<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">Right</span> -<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">Left</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$diff</span> *<span style="color:#79c0ff">$diff</span></span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p><code>Zip</code> pairs expected and predicted values.<code>Sum-Value</code> starts from a<code>Value</code> named<code>loss</code> and keeps adding selected terms. Because every subtraction, multiplication, and addition returns another<code>Value</code>, the loss is also a scalar root of a full computation graph.</p><h2 id="one-training-step" class="ps-heading">One Training Step<a class="ps-heading-anchor" href="#one-training-step" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>One optimization step follows the same shape as PyTorch, but without hiding anything:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">17</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">18</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">19</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">20</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">21</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">22</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">23</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">24</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">25</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">26</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$p</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$net</span>.<span style="color:#79c0ff">parameters</span>()) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$p</span>.<span style="color:#79c0ff">grad</span> =<span style="color:#a5d6ff">0.0</span></span></span><span style="display:flex;"><span>}</span></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$row</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$xs</span>) {</span></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$v</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$row</span>) {<span style="color:#79c0ff">$v</span>.<span style="color:#79c0ff">grad</span> =<span style="color:#a5d6ff">0.0</span> }</span></span><span style="display:flex;"><span>}</span></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$y</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$ys</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$y</span>.<span style="color:#79c0ff">grad</span> =<span style="color:#a5d6ff">0.0</span></span></span><span style="display:flex;"><span>}</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$ypred</span> =<span style="color:#79c0ff">$xs</span> | ForEach-Object {<span style="color:#79c0ff">$net</span>.<span style="color:#79c0ff">Invoke</span>(<span style="color:#79c0ff">$_</span>)[<span style="color:#a5d6ff">0</span>] }</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$loss</span> = Zip -Left<span style="color:#79c0ff">$ys</span> -Right<span style="color:#79c0ff">$ypred</span> | Sum-Value {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$diff</span> =<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">Right</span> -<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">Left</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$diff</span> *<span style="color:#79c0ff">$diff</span></span></span><span style="display:flex;"><span>}</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$loss</span>.<span style="color:#79c0ff">grad</span> =<span style="color:#a5d6ff">1.0</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$bpLossGraph</span> = New-BackpropagationGraph -val<span style="color:#79c0ff">$loss</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Get-GraphTopologicalSort -Graph<span style="color:#79c0ff">$bpLossGraph</span> -Reverse |</span></span><span style="display:flex;"><span> ForEach-Object {<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">OriginalObject</span> } |</span></span><span style="display:flex;"><span> ForEach-Object { &amp;<span style="color:#79c0ff">$_</span>.<span style="color:#79c0ff">backward</span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$p</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$net</span>.<span style="color:#79c0ff">parameters</span>()) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$p</span>.<span style="color:#79c0ff">data</span> +=<span style="color:#a5d6ff">-0.1</span> *<span style="color:#79c0ff">$p</span>.<span style="color:#79c0ff">grad</span></span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p>There are five phases: clear gradients, forward pass, loss construction, backward pass, parameter update. The learning rate is hard-coded as<code>0.1</code> because this is a notebook demo, not a training framework.</p><h2 id="training-loop" class="ps-heading">Training Loop<a class="ps-heading-anchor" href="#training-loop" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The notebook repeats that step 200 times. A shorter 80-epoch run shows the same
behavior: the sum of squared errors falls rapidly and then continues to converge.</p><p><img src="/images/articles/verso-micrograd-loss-history.svg" alt="Training loss over 80 epochs"/><p>The final notebook cell renders the full loss graph after training:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$lossGraph</span> = New-ExpressionGraph -val<span style="color:#79c0ff">$loss</span></span></span><span style="display:flex;"><span>Show-ExpressionGraph -Graph<span style="color:#79c0ff">$lossGraph</span> -rankdir<span style="color:#a5d6ff">'TD'</span></span></span></code></pre></td></tr></table></div></div><p>It is a useful stress test for<code>PSGraphView</code>, but it is too large for this page because it contains the complete scalar computation that produced the loss. That is also the point of micrograd: a neural network can be understood as a large scalar expression, and backpropagation is just the disciplined reverse walk over that expression.</p><h2 id="why-this-matters" class="ps-heading">Why This Matters<a class="ps-heading-anchor" href="#why-this-matters" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The important part is not that PowerShell is the best language for building neural networks. It is not. The point is that Verso makes PowerShell notebooks feel real again after the end of .NET Interactive, and the PowerShell kernel can now do the things notebook users expect: long-running host output, cancellation, persistent state, rich display, and ordinary module-based workflows.</p><p>For infrastructure engineers, that matters. The same mechanics used here for micrograd graphs apply to dependency graphs, Azure topology, policy validation, incident analysis, and any other workflow where PowerShell produces structured objects and the notebook should make those objects visible.</p>
]]></content:encoded></item><item><title>Validate Azure Resource Relationships with PSRule and PowerShell Graphs</title><link>https://powershell.org/articles/2026-07-24-validate-azure-resource-relationships-with-psrule-and-powershell-graphs/</link><guid>https://powershell.org/articles/2026-07-24-validate-azure-resource-relationships-with-psrule-and-powershell-graphs/</guid><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;PSRule for Azure makes it straightforward to validate the properties of individual
resources before deployment. But some architecture requirements describe a
relationship, not a property: every Function App must connect to the expected virtual
network, or every application must have exactly one private endpoint.&lt;/p&gt;
&lt;p&gt;This article shows how to collect those relationships in a PowerShell graph while
PSRule processes a Bicep deployment, then validate the completed graph at the end of
the pipeline.&lt;/p&gt;</description><content:encoded>&lt;![CDATA[<p>PSRule for Azure makes it straightforward to validate the properties of individual
resources before deployment. But some architecture requirements describe a
relationship, not a property: every Function App must connect to the expected virtual
network, or every application must have exactly one private endpoint.</p><p>This article shows how to collect those relationships in a PowerShell graph while
PSRule processes a Bicep deployment, then validate the completed graph at the end of
the pipeline.</p><h2 id="why-per-resource-rules-are-not-enough" class="ps-heading">Why per-resource rules are not enough<a class="ps-heading-anchor" href="#why-per-resource-rules-are-not-enough" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Consider an architecture with a Function App, a virtual network, an integration
subnet, and a private endpoint subnet. We can easily write rules that check:</p><ul><li>whether the Function App has public access disabled;</li><li>whether the virtual network uses the correct address space;</li><li>whether the expected subnets exist.</li></ul><p>Those checks still do not prove that the Function App is connected to the correct
subnet or that its private endpoint belongs to the expected virtual network. The
required information is spread across several expanded ARM resources.</p><p>A graph is a natural representation of this problem:</p><ul><li>resources and subnets become vertices;</li><li>references between them become edges;</li><li>an architecture requirement becomes a path or edge-count assertion.</li></ul><h2 id="prepare-psrule-and-the-graph-module" class="ps-heading">Prepare PSRule and the graph module<a class="ps-heading-anchor" href="#prepare-psrule-and-the-graph-module" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The example uses<a href="https://github.com/Azure/PSRule.Rules.Azure">PSRule.Rules.Azure</a> to expand and
analyze Bicep and<a href="https://github.com/eosfor/PSGraph">PSQuickGraph</a> to build the dependency graph.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Install-Module -Name PSRule.<span style="color:#79c0ff">Rules</span>.<span style="color:#79c0ff">Azure</span> -Scope CurrentUser</span></span><span style="display:flex;"><span>Install-Module -Name PSQuickGraph -Scope CurrentUser</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Import-Module PSQuickGraph</span></span></code></pre></td></tr></table></div></div><p>In<code>ps-rule.yaml</code>, enable Bicep expansion and include the convention that will collect
relationships:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">include</span>:<span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">module</span>:<span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/>-<span style="color:#a5d6ff">PSRule.Rules.Azure</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/>-<span style="color:#a5d6ff">PSQuickGraph</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">convention</span>:<span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">include</span>:<span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/>-<span style="color:#a5d6ff">FullConnectivityTest</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">configuration</span>:<span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">AZURE_BICEP_FILE_EXPANSION</span>:<span style="color:#6e7681"/><span style="color:#79c0ff">true</span><span style="color:#6e7681"/></span></span></code></pre></td></tr></table></div></div><h2 id="collect-relationships-with-a-convention" class="ps-heading">Collect relationships with a convention<a class="ps-heading-anchor" href="#collect-relationships-with-a-convention" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>A PSRule<a href="https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Conventions/">convention</a>
can run custom PowerShell at different stages of the pipeline. Its<code>Process</code> block
runs once for each input object, while its<code>End</code> block runs after all objects have
been processed.</p><p>That lifecycle gives us a convenient two-phase approach:</p><ol><li>Add every relevant resource and relationship to a graph.</li><li>Validate the graph only after the complete deployment has been seen.</li></ol><p>The following is a simplified version of the convention. The example uses
child-to-parent edges so a valid dependency chain ends at the virtual network.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">16</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">17</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">18</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">19</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">20</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">21</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">22</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">23</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">24</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">25</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">26</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">27</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">28</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">29</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">30</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">31</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">32</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">33</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">34</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">35</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">36</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">37</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">38</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">39</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">40</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">41</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">42</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">43</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">44</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">45</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">46</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">47</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">48</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">49</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">50</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#79c0ff">$global:vnetId</span> =<span style="color:#79c0ff">$null</span></span></span><span style="display:flex;"><span><span style="color:#79c0ff">$global:webSites</span> =<span style="color:#79c0ff">@</span>()</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$global:connectionGraph</span> = New-Graph</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$global:privateEndpointGraph</span> = New-Graph</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Export-PSRuleConvention<span style="color:#a5d6ff">'FullConnectivityTest'</span> -Process {</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (<span style="color:#79c0ff">$TargetObject</span>.Type<span style="color:#ff7b72;font-weight:bold">-eq</span><span style="color:#a5d6ff">'Microsoft.Network/virtualNetworks'</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$global:vnetId</span> =<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Id</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$subnetResource</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$TargetObject</span>.Resources) {</span></span><span style="display:flex;"><span> Add-Edge `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$subnetResource</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:connectionGraph</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span> Add-Edge `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$subnetResource</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:privateEndpointGraph</span></span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (<span style="color:#79c0ff">$TargetObject</span>.Type<span style="color:#ff7b72;font-weight:bold">-eq</span><span style="color:#a5d6ff">'Microsoft.Web/sites'</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$vnetIntegration</span> =<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Resources</span> |</span></span><span style="display:flex;"><span> Where-Object Type<span style="color:#ff7b72;font-weight:bold">-eq</span><span style="color:#a5d6ff">'Microsoft.Web/sites/networkConfig'</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#79c0ff">$global:webSites</span> +=<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Id</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span> Add-Edge `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$vnetIntegration</span>.<span style="color:#79c0ff">Properties</span>.<span style="color:#79c0ff">SubnetResourceId</span> `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:connectionGraph</span></span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (<span style="color:#79c0ff">$TargetObject</span>.Type<span style="color:#ff7b72;font-weight:bold">-eq</span><span style="color:#a5d6ff">'Microsoft.Network/privateEndpoints'</span>) {</span></span><span style="display:flex;"><span> Add-Edge `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Properties</span>.<span style="color:#79c0ff">Subnet</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:privateEndpointGraph</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$connection</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Properties</span>.PrivateLinkServiceConnections) {</span></span><span style="display:flex;"><span> Add-Edge `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$connection</span>.<span style="color:#79c0ff">Properties</span>.<span style="color:#79c0ff">PrivateLinkServiceId</span> `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$TargetObject</span>.<span style="color:#79c0ff">Id</span> `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:privateEndpointGraph</span></span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span>} -End {</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># The completed graphs are validated here.</span></span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p>The sample uses global variables because the state must remain available across
PSRule callback invocations. In a larger rule set, wrap this state in a single object
and reset it before each run.</p><h2 id="validate-complete-dependency-paths" class="ps-heading">Validate complete dependency paths<a class="ps-heading-anchor" href="#validate-complete-dependency-paths" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Once PSRule reaches the<code>End</code> block, every relevant Bicep resource has been expanded
and processed. We can now ask questions about the deployment as a whole.</p><p>For example, the following check confirms that every Function App has a VNet
integration path:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$webApp</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$global:webSites</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$path</span> = Get-GraphPath `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$webApp</span> `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$global:vnetId</span> `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:connectionGraph</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (<span style="color:#79c0ff">$null</span><span style="color:#ff7b72;font-weight:bold">-eq</span><span style="color:#79c0ff">$path</span>) {</span></span><span style="display:flex;"><span><span style="color:#ff7b72">throw</span><span style="color:#a5d6ff">"No VNet integration path was found for Function App:</span><span style="color:#79c0ff">$webApp</span><span style="color:#a5d6ff">"</span></span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p>We can apply the same idea to private endpoint connectivity:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#ff7b72">foreach</span> (<span style="color:#79c0ff">$webApp</span><span style="color:#ff7b72">in</span><span style="color:#79c0ff">$global:webSites</span>) {</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$path</span> = Get-GraphPath `</span></span><span style="display:flex;"><span> -From<span style="color:#79c0ff">$webApp</span> `</span></span><span style="display:flex;"><span> -To<span style="color:#79c0ff">$global:vnetId</span> `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:privateEndpointGraph</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (<span style="color:#79c0ff">$null</span><span style="color:#ff7b72;font-weight:bold">-eq</span><span style="color:#79c0ff">$path</span>) {</span></span><span style="display:flex;"><span><span style="color:#ff7b72">throw</span><span style="color:#a5d6ff">"No private endpoint path was found for Function App:</span><span style="color:#79c0ff">$webApp</span><span style="color:#a5d6ff">"</span></span></span><span style="display:flex;"><span> }</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table></div></div><p>Throwing from the<code>End</code> block causes the validation run to fail. This works well in
CI, although it does not produce the same detailed assertion output as a normal
PSRule<code>Rule</code> block.</p><p>Run the rules against the Bicep entry point with:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Invoke-PSRule -Format File -InputPath ./deployments/non-prod/main.<span style="color:#79c0ff">bicep</span></span></span></code></pre></td></tr></table></div></div><h2 id="export-the-graph-for-troubleshooting" class="ps-heading">Export the graph for troubleshooting<a class="ps-heading-anchor" href="#export-the-graph-for-troubleshooting" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The same model used for validation can produce a diagram. This is particularly
helpful when a CI check reports a missing path and you need to see which relationship
was absent. PSQuickGraph can export the graph in Graphviz DOT format; the<a href="https://graphviz.org/download/">Graphviz</a><code>dot</code> executable can then render it as SVG.</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">8</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>Export-Graph `</span></span><span style="display:flex;"><span> -Graph<span style="color:#79c0ff">$global:privateEndpointGraph</span> `</span></span><span style="display:flex;"><span> -Format Graphviz `</span></span><span style="display:flex;"><span> -Path ./output/private-endpoints.<span style="color:#79c0ff">dot</span></span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>&amp; dot `</span></span><span style="display:flex;"><span> -Tsvg ./output/private-endpoints.<span style="color:#79c0ff">dot</span> `</span></span><span style="display:flex;"><span> -o ./output/private-endpoints.<span style="color:#79c0ff">svg</span></span></span></code></pre></td></tr></table></div></div><p><img src="/images/articles/psrule-azure-resource-relationships.svg" alt="An Azure Function App connected through a private endpoint and subnet to a virtual network"/><p>This separates the solution into three clear steps:</p><ol><li>PSRule expands Bicep into resource objects.</li><li>A convention converts cross-resource references into graph edges.</li><li>Graph queries validate the architecture after all resources are available.</li></ol><p>The result complements normal per-resource rules instead of replacing them. Use
regular PSRule assertions for local properties and graph assertions for requirements
that span the deployment.</p><p>A complete Bicep project, PSRule configuration, architecture document, and runnable
Codespaces environment are available in the<a href="https://github.com/eosfor/psrule-demo">psrule-demo repository</a>.</p>
]]></content:encoded></item><item><title>How to Write for PowerShell.org</title><link>https://powershell.org/articles/2026-06-23-how-to-write-for-powershell-org/</link><guid>https://powershell.org/articles/2026-06-23-how-to-write-for-powershell-org/</guid><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><description>&lt;p&gt;There&amp;rsquo;s a thought that stops a lot of good articles: &lt;em&gt;who am I to write for
PowerShell.org?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s our brains safety net from the (highly unlikely) possibility of getting
denied. Or worse! Accepted! And now we&amp;rsquo;re forever on the hook to be an expert.
But it&amp;rsquo;s not true.&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t need to be an &lt;strong&gt;MVP&lt;/strong&gt;. You don&amp;rsquo;t need a &lt;strong&gt;blog&lt;/strong&gt;, a &lt;strong&gt;following&lt;/strong&gt;, or
a &lt;strong&gt;clever opinion&lt;/strong&gt; about the pipeline. You need one thing you figured out that
the documentation didn&amp;rsquo;t explain well. The &lt;code&gt;-Filter&lt;/code&gt; quirk that cost you and
afternoon. The script that finally tamed a chore you&amp;rsquo;d been doing by hand for a
year. If it helped you, it&amp;rsquo;ll help someone else who&amp;rsquo;s about to lose the same
afternoon.&lt;/p&gt;</description><content:encoded>&lt;![CDATA[<p>There&rsquo;s a thought that stops a lot of good articles:<em>who am I to write for
PowerShell.org?</em></p><p>It&rsquo;s our brains safety net from the (highly unlikely) possibility of getting
denied. Or worse! Accepted! And now we&rsquo;re forever on the hook to be an expert.
But it&rsquo;s not true.</p><p>You don&rsquo;t need to be an<strong>MVP</strong>. You don&rsquo;t need a<strong>blog</strong>, a<strong>following</strong>, or
a<strong>clever opinion</strong> about the pipeline. You need one thing you figured out that
the documentation didn&rsquo;t explain well. The<code>-Filter</code> quirk that cost you and
afternoon. The script that finally tamed a chore you&rsquo;d been doing by hand for a
year. If it helped you, it&rsquo;ll help someone else who&rsquo;s about to lose the same
afternoon.</p><div class="ps-alert ps-alert-important" role="note"><p class="ps-alert-title"><i class="fas fa-bullhorn" aria-hidden="true"/>Important</p><div class="ps-alert-body"><p>And here&rsquo;s the part that should lower your blood pressure:<strong>nothing you submit
goes live unreviewed</strong>.</p></div></div><p>A maintainer reads every submission, helps shape it, and
edits for clarity and formatting before it publishes. You are not flipping a
switch that broadcasts your rough draft to the world. You&rsquo;re starting a
conversation with people who want you to succeed.</p><p>So let&rsquo;s get you published. There are two ways in, so pick the one that matches
how comfortable you are with Git because both land in the same place.</p><h2 id="path-a-the-github-issue-no-git-required" class="ps-heading">Path A: The GitHub issue (no Git required)<a class="ps-heading-anchor" href="#path-a-the-github-issue-no-git-required" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>If &ldquo;fork the repo&rdquo; already made you tense up, this path is for you. You&rsquo;ll never
touch a command line.</p><p>Open the<a href="https://github.com/PowerShellOrg/PowerShellOrgWebsite/issues/new?template=guest-blog-post.yml">guest blog post
form</a>
and fill it out. The form does the structuring for you. It asks for exactly what
we need and nothing else:</p><ul><li><strong>Article title</strong> and<strong>your name</strong> as you&rsquo;d like it displayed.</li><li><strong>Submission type</strong>, and this is the part people miss: you can choose<em>&ldquo;Pitch
&ndash; I&rsquo;d like feedback before writing.&rdquo;</em> You don&rsquo;t have to show up with a
finished draft. Float the idea first, and a maintainer will tell you if it&rsquo;s a
fit and help you shape the angle before you spend the writing time.</li><li><strong>Description</strong>, one or two sentences. This becomes your SEO blurb and social
card, so it earns its keep.</li><li><strong>Category</strong> and<strong>tags</strong> (more on choosing these well below).</li><li><strong>Article content</strong>, where you paste your full Markdown if you have a draft.
Leave it blank if you&rsquo;re pitching.</li></ul><p>Submit it, and the rest happens in the issue thread. That&rsquo;s the whole path. No
branches, no merge conflicts, no Git vocabulary.</p><h2 id="path-b-the-pull-request-for-the-git-comfortable" class="ps-heading">Path B: The pull request (for the Git-comfortable)<a class="ps-heading-anchor" href="#path-b-the-pull-request-for-the-git-comfortable" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>If you already live in Git, you can submit the article directly and watch it
flow through the same review.</p><ol><li><p>Fork the repo and create a branch for your article.</p></li><li><p>Add a Markdown file in<code>content/articles/</code> using the date-slug naming
convention:</p><pre tabindex="0"><code>content/articles/YYYY-MM-DD-your-article-slug.md</code></pre></li><li><p>Start it with this front matter:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">12</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">13</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">14</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">15</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>---</span></span><span style="display:flex;"><span>title: "Your Article Title"</span></span><span style="display:flex;"><span>description: "A 1-2 sentence summary used for SEO, social cards, and the article list."</span></span><span style="display:flex;"><span>author: Your Name</span></span><span style="display:flex;"><span>authors:</span></span><span style="display:flex;"><span><span style="color:#ff7b72">-</span> Your Name</span></span><span style="display:flex;"><span>date: "YYYY-MM-DDT00:00:00+00:00"</span></span><span style="display:flex;"><span>categories:</span></span><span style="display:flex;"><span><span style="color:#ff7b72">-</span> Category Name</span></span><span style="display:flex;"><span>tags:</span></span><span style="display:flex;"><span><span style="color:#ff7b72">-</span> tag1</span></span><span style="display:flex;"><span><span style="color:#ff7b72">-</span> tag2</span></span><span style="display:flex;"><span>---</span></span><span style="display:flex;"><span/></span><span style="display:flex;"><span>Your article in Markdown goes here.</span></span></code></pre></td></tr></table></div></div></li><li><p>Open a pull request with a short description, and we&rsquo;ll review it there.</p></li></ol><div class="ps-alert ps-alert-tip" role="note"><p class="ps-alert-title"><i class="fas fa-lightbulb" aria-hidden="true"/>Let VS Code do the boring part!</p><div class="ps-alert-body"><p>Install the<a href="https://frontmatter.codes/">Front Matter CMS</a> extension, open the
repo, and run<strong>&ldquo;Create content&rdquo;</strong> in the<code>content/articles</code> folder. It
scaffolds the<code>YYYY-MM-DD-slug.md</code> filename and every front-matter field for
you, and it gives you a form for the title, description, category, and tags
instead of a wall of YAML you can typo. It turns the single most error-prone
step into a fill-in-the-blanks. If you only adopt one tool from this article,
make it this one.</p></div></div><h2 id="best-practices-that-get-you-published-faster" class="ps-heading">Best practices that get you published faster<a class="ps-heading-anchor" href="#best-practices-that-get-you-published-faster" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>None of these are gates. They&rsquo;re the small things that mean a maintainer spends
their time on your ideas instead of your formatting.</p><ul><li><strong>Open by telling readers what they&rsquo;ll walk away with.</strong> A two-sentence intro
that promises a payoff beats a warm-up paragraph every time.</li><li><strong>Write in Markdown, and fence your code with a language hint.</strong> Use<code>```powershell</code> so your samples get syntax highlighting instead of a gray
slab.</li><li><strong>Run your code before you paste it.</strong> A snippet that works on the first try
is the difference between a reader trusting you and a reader closing the tab.</li><li><strong>Keep the title concrete.</strong> &ldquo;Speed up your console with PSReadLine predictive
IntelliSense&rdquo; tells me what I&rsquo;m getting. &ldquo;PowerShell tips&rdquo; tells me nothing.</li></ul><p>That&rsquo;s the bar. It&rsquo;s lower than the one in your head.</p><h2 id="categories-and-tags-are-how-people-find-you" class="ps-heading">Categories and tags are how people find you<a class="ps-heading-anchor" href="#categories-and-tags-are-how-people-find-you" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>It&rsquo;s tempting to treat these as paperwork and pick whatever&rsquo;s first in the list.
Don&rsquo;t. They&rsquo;re the difference between an article that&rsquo;s read once and one that
keeps getting found.</p><p>Pick the single<strong>category</strong> that fits best. The current set:</p><blockquote><p>Announcements - Books - DevOps - Events - Graph - In Case You Missed It -
News - PowerShell Summit - PowerShell for Admins - PowerShell for Developers -
Scripting Games - Tips and Tricks - Tools - Training - Tutorials</p></blockquote><p>Category is the big bucket. It&rsquo;s how someone browsing &ldquo;PowerShell for Admins&rdquo;
stumbles onto your piece months from now.<strong>Tags</strong> are the specific hooks: the
cmdlets, modules, and concepts your article actually touches (<code>psreadline</code>,<code>regex</code>,<code>azure</code>,<code>pester</code>). Three to five honest, specific tags beat a dozen
vague ones. Tag what&rsquo;s really in the article, not every PowerShell word you can
think of, and your post surfaces next to its actual neighbors.</p><h2 id="claim-your-author-page" class="ps-heading">Claim your author page<a class="ps-heading-anchor" href="#claim-your-author-page" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>Once you&rsquo;re credited on an article, you can give yourself a real author page at<code>/authors/&lt;your-name&gt;/</code>: an avatar, a tagline, a short bio, and links back to
your own site and socials. Every article you write points back to it. It&rsquo;s a
small, durable corner of the PowerShell community that&rsquo;s<em>yours</em>, and it builds
with each post.</p><p>It&rsquo;s opt-in. Skip it and your byline still works exactly as before. But it takes
about five minutes, so why leave it on the table? You can see an example of mine
at the bottom.</p><p>Your profile is a single file at<code>content/authors/&lt;slug&gt;/_index.md</code>. The one
rule that trips people up: the<code>&lt;slug&gt;</code> has to match your byline exactly
(lowercased, spaces to hyphens), or the page attaches to nothing.</p><p>So let the helper script handle it:</p><div class="code-terminal ps-terminal rounded-xl overflow-hidden shadow-xl my-6"><div class="flex items-center px-4 py-3 border-b border-gray-700"><div class="flex space-x-2 mr-4"><div class="w-3 h-3 rounded-full bg-red-500"/><div class="w-3 h-3 rounded-full bg-yellow-500"/><div class="w-3 h-3 rounded-full bg-green-500"/></div><span class="text-gray-400 text-sm font-mono">PowerShell</span></div><pre class="text-sm leading-relaxed"><code class="language-powershell">./tools/new-author.ps1 "Jane Doe"</code></pre></div><p>That scaffolds<code>content/authors/jane-doe/_index.md</code> with every field commented.
Fill in what you want, delete the rest:</p><div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 1</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 2</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 3</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 4</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 5</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 6</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 7</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 8</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"> 9</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">10</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">11</span></code></pre></td><td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#ff7b72">---</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">title</span>:<span style="color:#6e7681"/><span style="color:#a5d6ff">"Jane Doe"</span><span style="color:#6e7681"/><span style="color:#8b949e;font-style:italic"># required -- keep this as your byline name</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">preferred_name</span>:<span style="color:#6e7681"/><span style="color:#a5d6ff">"Jane"</span><span style="color:#6e7681"/><span style="color:#8b949e;font-style:italic"># optional -- changes only how your name displays</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">tagline</span>:<span style="color:#6e7681"/><span style="color:#a5d6ff">"Cloud automation, mostly."</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">gravatar_hash</span>:<span style="color:#6e7681"/><span style="color:#a5d6ff">"..."</span><span style="color:#6e7681"/><span style="color:#8b949e;font-style:italic"># MD5 of your lowercased email -- keeps your email private</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">github</span>:<span style="color:#6e7681"/><span style="color:#a5d6ff">"https://github.com/janedoe"</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#7ee787">website</span>:<span style="color:#6e7681"/><span style="color:#a5d6ff">"https://janedoe.dev"</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#8b949e;font-style:italic"># twitter / mastodon / linkedin / bluesky also supported</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#ff7b72">---</span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/></span></span><span style="display:flex;"><span><span style="color:#6e7681"/><span style="color:#a5d6ff">Your bio in Markdown goes here.</span><span style="color:#6e7681"/></span></span></code></pre></td></tr></table></div></div><p>One thoughtful detail worth calling out: you can set an avatar<strong>without</strong>
putting your email address in a public repo. Store the MD5 hash of your
lowercased email as<code>gravatar_hash</code>, and Gravatar serves your picture while your
email stays private:</p><div class="code-terminal ps-terminal rounded-xl overflow-hidden shadow-xl my-6"><div class="flex items-center px-4 py-3 border-b border-gray-700"><div class="flex space-x-2 mr-4"><div class="w-3 h-3 rounded-full bg-red-500"/><div class="w-3 h-3 rounded-full bg-yellow-500"/><div class="w-3 h-3 rounded-full bg-green-500"/></div><span class="text-gray-400 text-sm font-mono">PowerShell</span></div><pre class="text-sm leading-relaxed"><code class="language-powershell">$email = "jane@example.com"
[System.BitConverter]::ToString(
[System.Security.Cryptography.MD5]::Create().ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($email.Trim().ToLowerInvariant())
)
).Replace("-", "").ToLowerInvariant()</code></pre></div><p>And if your name ever changes,<code>./tools/new-author.ps1 "Old Name" -To "New Name"</code>
rewrites your byline across every article and adds a redirect so your old
profile URL keeps working. Open a PR with the result.</p><h2 id="your-turn" class="ps-heading">Your turn<a class="ps-heading-anchor" href="#your-turn" aria-label="Link to this section" title="Link to this section"><i class="fas fa-link" aria-hidden="true"/></a></h2><p>The whole point of PowerShell.org is that it&rsquo;s built by the people who use
PowerShell, and that includes you. You don&rsquo;t have to be sure it&rsquo;s good enough.
That&rsquo;s literally what the review is for. Pitch the idea, paste the draft, or
send the PR, and you won&rsquo;t be doing it alone. There&rsquo;s a community on the other
side of that submit button that wants to help you get it across the line.</p><p><a href="https://github.com/PowerShellOrg/PowerShellOrgWebsite/issues/new?template=guest-blog-post.yml">Start here.</a></p>
]]></content:encoded></item></channel></rss>