<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://teramako.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://teramako.github.io/" rel="alternate" type="text/html" hreflang="ja" /><updated>2026-09-13T17:29:18+09:00</updated><id>https://teramako.github.io/feed.xml</id><title type="html">hogehoge</title><subtitle>メモ置き場。</subtitle><author><name>teramako</name><email>terama@gmail.com</email></author><entry><title type="html">PowerShell で Markdown Viewer/Editor の GUI</title><link href="https://teramako.github.io/2026/09/13/Markdown-viewer-with-PowerShell.html" rel="alternate" type="text/html" title="PowerShell で Markdown Viewer/Editor の GUI" /><published>2026-09-13T17:25:00+09:00</published><updated>2026-09-13T17:25:00+09:00</updated><id>https://teramako.github.io/2026/09/13/Markdown-viewer-with-PowerShell</id><content type="html" xml:base="https://teramako.github.io/2026/09/13/Markdown-viewer-with-PowerShell.html"><![CDATA[<ul>
<li><a href="https://github.com/teramako/PSMarkdownEditor">https://github.com/teramako/PSMarkdownEditor</a></li>
</ul>
<p>PowerShell 7 で GUI（WinForms）を使って、Markdown Viewer/Editor を作ってみた。</p>
<p><img src="https://github.com/user-attachments/assets/3bbce5a1-53e7-4770-948b-780c74fdc6e5" alt="screenshot" /></p>
<h2 id="section">機能</h2>
<h3 id="editor-">Editor 部</h3>
<ul>
<li>基本的にはただの TextBox</li>
<li>Live editor として機能</li>
<li>外部からファイルのドラッグ＆ドロップ可能
<ul>
<li><code>*.md</code> なら、そのファイルを開く</li>
<li>画像(<code>*.(png|gif|jpe?g|webp|svg)</code>) ファイルなら、カーソル位置へ <code>![](file:///...)</code> を挿入</li>
</ul>
</li>
</ul>
<h3 id="browser-">Browser 部</h3>
<ul>
<li>HTMLレンダリングには <a href="https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/" title="Microsoft Edge WebView2 | Microsoft Edge Developer">WebView2</a> を使用</li>
<li>GitHub Markdown 的なCSS</li>
<li>外部からファイルのドラッグ＆ドロップ可能
<ul>
<li><code>*.md</code> なら、そのファイルを開く</li>
</ul>
</li>
</ul>
<h4 id="html">HTMLレンダリング</h4>
<p>起動すると <code>MDView.html</code> が読み込まれる。</p>
<p>エディタ部分のmarkdownテキストは PowerShell 7 内臓の <code>ConvertFrom-Markdown</code> (<a href="https://github.com/xoofx/markdig" title="xoofx/markdig: A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET">markdig</a>)で HTML に変換され、<code>MDView.html</code> 内の JavaScript 関数に渡される。</p>
<p>コードブロックのハイライトとコピーのために、<a href="https://highlightjs.org/" title="highlight.js">highlight.js</a> と <a href="https://github.com/arronhunt/highlightjs-copy" title="arronhunt/highlightjs-copy: 📋❇️ A simple, accessible highlightjs plugin to add a copy button to your code blocks.">highlightjs-copy</a> を読み込むようにしている。</p>
<p>この辺りや CSS が気に入らなければ、好きに弄ってくれ。</p>
<h3 id="file">Fileメニュー</h3>
<img width="148" height="126" alt="File Menu" src="https://github.com/user-attachments/assets/5894f326-3b2b-4a9d-8c07-f8cebedfe6a4" />
<p>特に言うこと無し</p>
<h3 id="view">Viewメニュー</h3>
<img width="192" height="172" alt="View Menu" src="https://github.com/user-attachments/assets/bde9da28-3b0f-42f8-8a35-218bcf62a45c" />
<ul>
<li>TopMost: ウィンドウを常に全面に出すやつ</li>
<li>SplitView: エディタ部分とブラウザ部分を両方表示</li>
<li>Editor Only: エディタ部分のみ表示</li>
<li>Browser Only: ブラウザ部分のみ表示</li>
<li>Preview Mode:
<ul>
<li>ファイルの更新監視をして、ブラウザへ反映させる</li>
<li>ファイルを開いている時のみ有効</li>
<li>好きなエディタでMarkdownファイルを開いて更新してね</li>
</ul>
</li>
<li>Font: エディタ部分のフォントを変更</li>
</ul>
<h2 id="section-1">セットアップ</h2>
<p>PowerShell 7 以上(Windows標準でインストールされているPowerShell 5.1ではない)で
<code>https://github.com/teramako/PSMarkdownEditor/blob/main/setup.ps1</code> を実行してもらう。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pwsh</span><span class="w"> </span><span class="nt">-NoProfile</span><span class="w"> </span><span class="nt">-File</span><span class="w"> </span><span class="nx">path\to\setup.ps1</span><span class="w">
</span></code></pre></div></div>
<p>実行すると <code>setup.ps1</code> があるディレクトリに以下を行う</p>
<ol>
<li><a href="https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/" title="Microsoft Edge WebView2 | Microsoft Edge Developer">WebView2</a> のセットアップ
<ul>
<li>WebView2 が使用するデータ保管用ディレクトリ(<code>webview2_userdata</code>)</li>
<li>.NET 用ライブラリを <a href="http://www.nuget.org">www.nuget.org</a> からダウンロード</li>
<li>ダウンロードしたファイル(zip形式)中から必要な dll ファイルを <code>libs</code> ディレクトリに展開</li>
</ul>
</li>
<li>起動用ショートカット(<code>MDView.lnk</code>)の作成
<ul>
<li><code>conhost.exe --headless</code> 経由でスクリプト(ps1)実行することで、コマンドプロンプトが一瞬表示されるのを防止</li>
</ul>
</li>
</ol>
<p>あとは、<code>MDView.lnk</code> を実行すれば起動する</p>
<h2 id="tips">苦労話とか開発Tips</h2>
<h3 id="systemwindowsformsform-">System.Windows.Forms.Form を継承したクラス作成</h3>
<p>当初、普通に C# で書くような感じで、<code>System.Windows.Forms.Form</code> を継承したクラスを作って使用しようと、以下のように書いた。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">System.Windows.Forms</span><span class="p">;</span><span class="w">
</span><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">System.Drawing</span><span class="p">;</span><span class="w">
</span><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">Microsoft.Web.WebView2.Core</span><span class="p">;</span><span class="w">
</span><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">Microsoft.Web.WebView2.WinForms</span><span class="p">;</span><span class="w">

</span><span class="c"># ..</span><span class="w">

</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Windows.Forms</span><span class="p">;</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Drawing</span><span class="p">;</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="nx">\libs\Microsoft.Web.WebView2.Core.dll</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="nx">\libs\Microsoft.Web.WebView2.WinForms.dll</span><span class="w">

</span><span class="kr">class</span><span class="w"> </span><span class="nc">MDView</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nc">Forms</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="c"># ...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>ところが、これではうまく行かない。</p>
<p><code>Add-Type</code> による dll 読み込みより先にクラス定義が読み込まれてしまうのだ。
アセンブリ参照が足りない、みたいなエラーになって怒られる。</p>
<p>そこで、2ファイルに分ける対応をした。</p>
<p><code>MDView.ps1</code>:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Windows.Forms</span><span class="p">;</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.Drawing</span><span class="p">;</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="nx">\libs\Microsoft.Web.WebView2.Core.dll</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="nx">\libs\Microsoft.Web.WebView2.WinForms.dll</span><span class="w">

</span><span class="o">.</span><span class="w"> </span><span class="p">(</span><span class="n">Join-Path</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="w"> </span><span class="nx">MDView.class.ps1</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>
<p><code>MDView.class.ps1</code>:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">System.Windows.Forms</span><span class="p">;</span><span class="w">
</span><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">System.Drawing</span><span class="p">;</span><span class="w">
</span><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">Microsoft.Web.WebView2.Core</span><span class="p">;</span><span class="w">
</span><span class="kr">using</span><span class="w"> </span><span class="kr">namespace</span><span class="w"> </span><span class="n">Microsoft.Web.WebView2.WinForms</span><span class="p">;</span><span class="w">

</span><span class="kr">class</span><span class="w"> </span><span class="nc">MDView</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nc">Forms</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="c"># ...</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>このように <strong><code>Add-Type</code> → クラス定義</strong> の順番を強制することで、PowerShell でも問題なく Form を継承したクラスを作れる。</p>
<h3 id="timer-">Timer による遅延レンダリング</h3>
<p>TextBox の <code>TextChanged</code> イベント発生毎に HTML レンダリングしていると重たくなる。</p>
<p>そこで <a href="https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.timer" title="Timer クラス (System.Windows.Forms) | Microsoft Learn"><code>System.Windows.Forms.Timer</code></a> を使用して、 <strong>一定時間入力が止まったらレンダリング</strong> する方式にした。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">class</span><span class="w"> </span><span class="nc">MDView</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nc">Form</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">Timer</span><span class="p">]</span><span class="w"> </span><span class="nv">$UpdateTimer</span><span class="w">
    </span><span class="p">[</span><span class="n">TextBox</span><span class="p">]</span><span class="w"> </span><span class="nv">$MarkdownTextBox</span><span class="w">
    </span><span class="n">MDView</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">UpdateTimer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">Timer</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Interval</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">500</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">MarkdownTextBox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Object</span><span class="w"> </span><span class="nx">TextBox</span><span class="w"> </span><span class="nt">-Property</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="err">...</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="p">[</span><span class="n">void</span><span class="p">]</span><span class="w"> </span><span class="n">Form_Load</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="w"> </span><span class="nv">$e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">MarkdownTextBox</span><span class="o">.</span><span class="nf">Add_TextChanged</span><span class="p">(</span><span class="bp">$this</span><span class="o">.</span><span class="nf">MarkdownTextBox_TextChanged</span><span class="p">)</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">UpdateTimer</span><span class="o">.</span><span class="nf">Add_Tick</span><span class="p">(</span><span class="bp">$this</span><span class="o">.</span><span class="nf">UpdateTimer_Tick</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="p">[</span><span class="n">void</span><span class="p">]</span><span class="w"> </span><span class="n">MarkdownTextBox_TextChanged</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="w"> </span><span class="nv">$e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">UpdateTimer</span><span class="o">.</span><span class="nf">Stop</span><span class="p">();</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">UpdateTimer</span><span class="o">.</span><span class="nf">Start</span><span class="p">();</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="p">[</span><span class="n">void</span><span class="p">]</span><span class="w"> </span><span class="n">UpdateTimer_Tick</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="w"> </span><span class="nv">$e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">UpdateTimer</span><span class="o">.</span><span class="nf">Stop</span><span class="p">();</span><span class="w">
        </span><span class="bp">$this</span><span class="o">.</span><span class="nf">UpdateView</span><span class="p">(</span><span class="bp">$null</span><span class="p">)</span><span class="w"> </span><span class="c"># ←ブラウザにHTMLレンダリングさせるメソッド</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><code>TextChanged</code> イベント発生時には Timer の停止、開始のみ行い、
Timer に設定された <code>Interval</code> (ミリ秒) 経過後に実際のHTMLレンダリングをさせる。</p>
<h3 id="section-2">ファイル監視</h3>
<p>好きなエディタで書いて保存すると自動でブラウザに反映させたくてファイル監視機能を付けようとした。</p>
<p><a href="https://learn.microsoft.com/ja-jp/dotnet/api/system.io.filesystemwatcher" title="FileSystemWatcher クラス (System.IO) | Microsoft Learn"><code>System.IO.FileSystemWather</code></a> を使用したのだが、最初は PowerShell がクラッシュしてしまった。</p>
<p>原因は、<strong>イベントが UI スレッドではなく ThreadPool スレッドで発火するため</strong> だそうで、PowerShell のコードが実行できず例外になる。</p>
<p>解決策は <a href="https://learn.microsoft.com/ja-jp/dotnet/api/system.io.filesystemwatcher.synchronizingobject">SynchronizingObject</a> を設定すること。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$fsw</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">IO.FileSystemWatcher</span><span class="p">]::</span><span class="n">new</span><span class="p">(</span><span class="bp">$this</span><span class="o">.</span><span class="nf">File</span><span class="o">.</span><span class="nf">DirectoryName</span><span class="p">,</span><span class="w"> </span><span class="bp">$this</span><span class="o">.</span><span class="nf">File</span><span class="o">.</span><span class="nf">Name</span><span class="p">)</span><span class="w">
</span><span class="nv">$fsw</span><span class="o">.</span><span class="nf">NotifyFilter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">IO.NotifyFilters</span><span class="p">]::</span><span class="n">LastWrite</span><span class="p">;</span><span class="w">
</span><span class="nv">$fsw</span><span class="o">.</span><span class="nf">IncludeSubdirectories</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="p">;</span><span class="w">
</span><span class="nv">$fsw</span><span class="o">.</span><span class="nf">SynchronizingObject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$this</span><span class="p">;</span><span class="w"> </span><span class="c"># ← UIスレッドでイベントを発行させる</span><span class="w">
</span><span class="nv">$fsw</span><span class="o">.</span><span class="nf">EnableRaisingEvents</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="p">;</span><span class="w">
</span><span class="nv">$fsw</span><span class="o">.</span><span class="nf">Add_Changed</span><span class="p">(</span><span class="bp">$this</span><span class="o">.</span><span class="nf">FileWatcher_Changed</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>
<h3 id="webview2-">WebView2 へのファイルのドラッグ＆ドロップ</h3>
<p>ブラウザ部分へ <code>*.md</code> ファイルをドロップすることで、そのファイルをHTMLレンダリングさせようとした。
通常なら <code>DragEnter</code> や <code>DragDrop</code> イベントをハンドラを設定するのだが、
WebView2 では <code>NewWindowRequested</code> イベントが発行される仕様なようで、このイベントのハンドラを設置する必要がある。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">$this</span><span class="o">.</span><span class="nf">WebView</span><span class="o">.</span><span class="nf">CoreWebView2</span><span class="o">.</span><span class="nf">Add_NewWindowRequested</span><span class="p">(</span><span class="bp">$this</span><span class="o">.</span><span class="nf">WebView_NewWindowRequested</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">void</span><span class="p">]</span><span class="w"> </span><span class="n">WebView_NewWindowRequested</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="n">CoreWebView2NewWindowRequestedEventArgs</span><span class="p">]</span><span class="w"> </span><span class="nv">$e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$uri</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">uri</span><span class="p">]::</span><span class="n">new</span><span class="p">(</span><span class="nv">$e</span><span class="o">.</span><span class="nf">Uri</span><span class="p">)</span><span class="w">
    </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$uri</span><span class="o">.</span><span class="nf">IsFile</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$e</span><span class="o">.</span><span class="nf">Handled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$true</span><span class="w">
        </span><span class="nv">$fileInfo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">IO.FileInfo</span><span class="p">]::</span><span class="n">new</span><span class="p">(</span><span class="nv">$uri</span><span class="o">.</span><span class="nf">LocalPath</span><span class="p">)</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$fileInfo</span><span class="o">.</span><span class="nf">Extension</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s2">".md"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="bp">$this</span><span class="o">.</span><span class="nf">TryOpenMarkdownFile</span><span class="p">(</span><span class="nv">$fileInfo</span><span class="p">)</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[https://github.com/teramako/PSMarkdownEditor]]></summary></entry><entry><title type="html">PowerShell でオプション付きエイリアスを設定する</title><link href="https://teramako.github.io/2026/08/19/Set-Alias-with-options-in-PowerShell.html" rel="alternate" type="text/html" title="PowerShell でオプション付きエイリアスを設定する" /><published>2026-08-19T02:00:00+09:00</published><updated>2026-08-19T02:00:00+09:00</updated><id>https://teramako.github.io/2026/08/19/Set-Alias-with-options-in-PowerShell</id><content type="html" xml:base="https://teramako.github.io/2026/08/19/Set-Alias-with-options-in-PowerShell.html"><![CDATA[<p>はっきり言って PowerShell のエイリアスは使いにくい。
コマンド・オプション付きのエイリアスを作れないところが特に。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-Alias</span><span class="w"> </span><span class="nx">ls</span><span class="w"> </span><span class="s1">'ls --color -F'</span><span class="w">
</span></code></pre></div></div>
<p>とかができない。</p>
<p>ということで、多少制限があるものの可能にするものを作った。</p>
<ul>
<li><a href="https://github.com/teramako/PSApplicationAlias">https://github.com/teramako/PSApplicationAlias</a></li>
</ul>
<h2 id="section">使用方法</h2>
<p>プロファイルに dot-source でスクリプトを読み込む形で使う。</p>
<blockquote>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="w"> </span><span class="bp">$PSScriptRoot</span><span class="n">\AppAlias.ps1</span><span class="w">
</span></code></pre></div></div>
</blockquote>
<p>読み込むと、<code>Set-ApplicationAlias</code> というコマンド（関数）が使えるようになる。</p>
<blockquote>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Remove built-in alias (AllScope) before redefining</span><span class="w">
</span><span class="n">Remove-Item</span><span class="w"> </span><span class="nx">Alias:ls</span><span class="p">,</span><span class="w"> </span><span class="nx">Alias:cat</span><span class="w">

</span><span class="n">Set-ApplicationAlias</span><span class="w"> </span><span class="nx">cat</span><span class="w"> </span><span class="nx">D:\Program\Git\usr\bin\cat.exe</span><span class="w">

</span><span class="c"># If the command is a path that contains spaces, set it in the format `&amp; "Command Name" ...`.</span><span class="w">
</span><span class="n">Set-ApplicationAlias</span><span class="w"> </span><span class="nx">ls</span><span class="w"> </span><span class="s1">'&amp; "C:\Program Files\coreutils\bin\ls.exe" --color -F'</span><span class="w">
</span></code></pre></div></div>
</blockquote>
<p>定義済みのエイリアス削除して(※)、<code>Set-ApplicationAlias</code> でエイリアス設定をする。</p>
<p>※: PowerShell の組み込み alias（<code>ls</code>, <code>cat</code>, <code>dir</code> など）は AllScope で定義されていて、対話セッション中に Set-Alias で上書きできない。PROFILE 内で Remove-Item しておく必要がある。</p>
<h2 id="section-1">内部動作</h2>
<p><code>Set-ApplicationAlias</code> 内部では PowerShell 本体の <code>Set-Alias</code> を呼んでエイリアス定義をしているが、Description プロパティに JSON 形式でコマンドパスと引数リストを埋め込んでいる。
そして、実行先の <code>Invoke-AliasApplication</code> という(dot-source で読み込んだスクリプト内で定義している)関数を固定的に設置している。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-Alias</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$Name</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nx">Invoke-AliasApplication</span><span class="w"> </span><span class="nt">-Description</span><span class="w"> </span><span class="nv">$json</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Global</span><span class="w">
</span></code></pre></div></div>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">&gt;</span><span class="w"> </span>Get-Alias <span class="nt">-Definition</span> Invoke-AliasApplication | <span class="k">select </span>CommandType,DisplayName,Description
<span class="go">
CommandType DisplayName                    Description
----------- -----------                    -----------
</span><span class="gp">      Alias cat -&gt;</span><span class="w"> </span>Invoke-AliasApplication <span class="o">{</span><span class="s2">"Command"</span>:<span class="s2">"D:</span><span class="se">\\</span><span class="s2">Program</span><span class="se">\\</span><span class="s2">Git</span><span class="se">\\</span><span class="s2">usr</span><span class="se">\\</span><span class="s2">bin</span><span class="se">\\</span><span class="s2">cat.exe"</span>,<span class="s2">"Args"</span>:<span class="o">{}}</span>
<span class="gp">      Alias ls -&gt;</span><span class="w"> </span>Invoke-AliasApplication  <span class="o">{</span><span class="s2">"Command"</span>:<span class="s2">"C:</span><span class="se">\\</span><span class="s2">Program Files</span><span class="se">\\</span><span class="s2">coreutils</span><span class="se">\\</span><span class="s2">bin</span><span class="se">\\</span><span class="s2">ls.exe"</span>,<span class="s2">"Args"</span>:[<span class="s2">"--color"</span>,<span class="s2">"-F"</span><span class="o">]}</span>
<span class="go">
</span></code></pre></div></div>
<p>※: <code>Get-ApplicationAlias</code> で上記と同等の結果が得られるようなコマンドもあるよ</p>
<p><code>cat</code> や <code>ls</code> を実行すると、<code>Invoke-AliasApplication</code> が呼ばれる。</p>
<p><code>Invoke-AliasApplication</code> 内では、実行元となったエイリアス名を取得し、そのエイリアスのDescriptionプロパティからコマンドパスと引数リストを得る。
あとは、パイプライン入力値と共にコマンドに引数を付けて実行する。</p>
<p>コード自体は、割と単純だ。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">Invoke-AliasApplication</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">begin</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$aliasName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$MyInvocation</span><span class="o">.</span><span class="nf">InvocationName</span><span class="w">
        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$aliasName</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="bp">$MyInvocation</span><span class="o">.</span><span class="nf">MyCommand</span><span class="o">.</span><span class="nf">Name</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="kr">throw</span><span class="w"> </span><span class="s2">"Don't call this function directly."</span><span class="w">
        </span><span class="p">}</span><span class="w">

        </span><span class="nv">$alias</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Alias</span><span class="w"> </span><span class="nv">$aliasName</span><span class="w">
        </span><span class="nv">$info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$alias</span><span class="o">.</span><span class="nf">Description</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ConvertFrom-Json</span><span class="w">
        </span><span class="nv">$cmdName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$info</span><span class="o">.</span><span class="nf">Command</span><span class="w">
        </span><span class="nv">$cmdArgs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$info</span><span class="o">.</span><span class="nf">Args</span><span class="w">

        </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="o">-not</span><span class="w"> </span><span class="nv">$cmdName</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="kr">throw</span><span class="w"> </span><span class="p">(</span><span class="s2">"Description of '{0}' may be incorrect: {1}"</span><span class="w"> </span><span class="nt">-f</span><span class="w"> </span><span class="nv">$aliasName</span><span class="p">,</span><span class="w"> </span><span class="nv">$alias</span><span class="o">.</span><span class="nf">Description</span><span class="p">);</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="kr">end</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="bp">$input</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">$cmdName</span><span class="w"> </span><span class="err">@</span><span class="n">cmdArgs</span><span class="w"> </span><span class="err">@</span><span class="nx">args</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>ポイントは、実行元となったエイリアス名の取得するところ。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$aliasName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$MyInvocation</span><span class="o">.</span><span class="nf">InvocationName</span><span class="w">
</span></code></pre></div></div>
<p>元々、<code>$MyInvocation.InvocationName</code> で Unix系の <code>$0</code> に似ているものが取れて面白いことができそうだと目を付けていたのが今回役に立った。</p>
<h2 id="section-2">制限: バイナリ入出力不可</h2>
<ul>
<li>バイナリデータが欲しい時には、この方法でのエイリアス設定はしない方が良い。</li>
</ul>
<p>まず、PowerShell 本体の動きの話から。</p>
<p>通常の Cmdlet や関数のパイプライン入出力は、その型のオブジェクトになる。
対して、<code>外部コマンド1 | 外部コマンド2</code> のような外部コマンドのパイプ間や、<code>外部コマンド &gt; file</code> のような外部コマンドのリダイレクトでは、外部コマンド同士の世界に閉じられるためパイプライン入出力がバイナリ・ストリーム直で行われるようになっている。
そして、外部コマンドと PowerShell のCmdletや関数、式が混在すると、バイナリ・ストリームは文字列(<code>string[]</code>)化されて PowerShell とのやり取りが行われる。</p>
<p>で、エイリアス設定すると PowerShell の関数でラップされるので、<code>外部コマンド1 | 外部コマンド2</code> のように見えてもバイナリ・ストリームでの入出力が行われず、文字列での入出力になってしまう。</p>
<p>文字コードや改行コードが変わったりするため、以下のような違いが出てしまう。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">&gt;</span><span class="w"> </span>Get-Command <span class="nb">cat</span>
<span class="go">
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
</span><span class="gp">Alias           cat -&gt;</span><span class="w"> </span>Invoke-AliasApplication
<span class="go">
</span><span class="gp">&gt;</span><span class="w"> </span><span class="nb">sha1sum</span> .<span class="se">\s</span>hift_jis.txt
<span class="go">46f0ea63e867da29af52457ea9e465af03dbb60b  ./shift_jis.txt
</span><span class="gp">&gt;</span><span class="w"> </span>D:<span class="se">\P</span>rogram<span class="se">\G</span>it<span class="se">\u</span>sr<span class="se">\b</span><span class="k">in</span><span class="se">\c</span>at.exe .<span class="se">\s</span>hift_jis.txt | <span class="nb">sha1sum</span>
<span class="go">46f0ea63e867da29af52457ea9e465af03dbb60b  -
</span><span class="gp">&gt;</span><span class="w"> </span><span class="nb">cat</span> .<span class="se">\s</span>hift_jis.txt | <span class="nb">sha1sum</span>
<span class="go">471399aeda50586bdc982b558bfde1da642c0fb3  -
</span></code></pre></div></div>
<p><code>cat</code> を ApplicationAlias でラップすると PowerShell の関数として実行されるため、 <strong>外部コマンド間のバイナリストリームではなく、文字列化されたパイプラインが流れる</strong>。その結果、文字コード変換や改行変換が入り、ハッシュ値が変わってしまう。</p>
<h2 id="cmdlet">制限: Cmdletには使えない</h2>
<ul>
<li>外部コマンドのエイリアス設定にしか使えない</li>
</ul>
<p>Cmdlet のパラメーターは <code>string[]</code> な配列ではなく、ディクショナリ(<code>Hashtable</code>) で設定する必要があったり、パラメーター値も文字列とは限らないなど、複雑なので今回は外部コマンドのみに限定した。</p>
<p>Cmdlet の場合のパラメーター値設定は <code>$PSDefaultParameterValues</code> (<a href="https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_parameters_default_values?view=powershell-7.6">about_Parameters_Default_Values - PowerShell | Microsoft Learn</a>) を使った方が良いだろうし。</p>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[はっきり言って PowerShell のエイリアスは使いにくい。 コマンド・オプション付きのエイリアスを作れないところが特に。]]></summary></entry><entry><title type="html">非同期処理をする PowerShell Cmdlet を作る</title><link href="https://teramako.github.io/2026/08/15/Async-Cmdlet.html" rel="alternate" type="text/html" title="非同期処理をする PowerShell Cmdlet を作る" /><published>2026-08-15T21:15:00+09:00</published><updated>2026-08-15T21:15:00+09:00</updated><id>https://teramako.github.io/2026/08/15/Async-Cmdlet</id><content type="html" xml:base="https://teramako.github.io/2026/08/15/Async-Cmdlet.html"><![CDATA[<p>PowerShell の Cmdlet で非同期処理に対応する方法。</p>
<ul>
<li><a href="https://github.com/JustinGrote/ExcelFast/blob/main/Source/PowerShell/Cmdlets/TaskCmdlet.cs">https://github.com/JustinGrote/ExcelFast/blob/main/Source/PowerShell/Cmdlets/TaskCmdlet.cs</a></li>
</ul>
<p>某所で紹介されていて、感動したので肝の部分を解説したい。</p>
<p><a href="https://github.com/teramako/Sashimi/" title="teramako/Sashimi: Raw process I/O for PowerShell — execute external commands and handle stdin/stdout/stderr as byte streams.">Sashimi</a> では、この方式を応用して、外部プロセスの stdout/stderr を非同期で読み取りつつ、
Cmdlet スレッド側でリアルタイムに出力する仕組みを構築している。</p>
<h2 id="cmdlet-">Cmdlet は非同期出力不可</h2>
<p>PowerShell の Cmdlet は同期的に動く。</p>
<ol>
<li>Begin (BeginProcessing) で初期化</li>
<li>Process (ProcessRecord) でパイプライン入力処理</li>
<li>End (EndProcessing) で最終的な処理</li>
</ol>
<p>のような流れで、PowerShell 本体から呼び出される。</p>
<p>各処理で非同期処理も可能ではあるものの、出力処理（<code>WriteObject</code> とか <code>WriteError</code> とか）はそのCmdletのメインスレッドからしか実行できない制約がある。</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="nf">Cmdlet</span><span class="p">(</span><span class="n">VerbsDiagnostic</span><span class="p">.</span><span class="n">Test</span><span class="p">,</span> <span class="s">"AsyncCmd"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">TestAsyncCommand</span> <span class="p">:</span> <span class="n">PSCmdlet</span>
<span class="p">{</span>
    <span class="p">[</span><span class="nf">Parameter</span><span class="p">()]</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Message</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="s">"Hello"</span><span class="p">;</span>

    <span class="k">private</span> <span class="n">Task</span><span class="p">?</span> <span class="n">_outputTask</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Stopwatch</span> <span class="n">_sw</span> <span class="p">=</span> <span class="n">Stopwatch</span><span class="p">.</span><span class="nf">StartNew</span><span class="p">();</span>

    <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">BeginProcessing</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="nf">WriteVerbose</span><span class="p">(</span><span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) Start: BeginProcessing"</span><span class="p">);</span>
        <span class="n">_outputTask</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="n">OutputAsync</span><span class="p">);</span>
        <span class="nf">WriteVerbose</span><span class="p">(</span><span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) End: BeginProcessing"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">EndProcessing</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="nf">WriteVerbose</span><span class="p">(</span><span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) Start: EndProcessing"</span><span class="p">);</span>
        <span class="k">try</span>
        <span class="p">{</span>
            <span class="n">_outputTask</span><span class="p">?.</span><span class="nf">Wait</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="k">catch</span> <span class="p">(</span><span class="n">AggregateException</span> <span class="n">ex</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="nf">WriteError</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="n">InnerException</span><span class="p">,</span> <span class="s">"AsyncError"</span><span class="p">,</span> <span class="n">ErrorCategory</span><span class="p">.</span><span class="n">NotSpecified</span><span class="p">,</span> <span class="k">this</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="nf">WriteVerbose</span><span class="p">(</span><span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) End: EndProcessing"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">OutputAsync</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">10</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
        <span class="p">{</span>
            <span class="nf">WriteObject</span><span class="p">(</span><span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) </span><span class="p">{</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
            <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="m">100</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>上記のように、 <code>BeginProcessing</code> 内で非同期処理を走らせ、<code>EndProcessing</code> で終了を待とうとするとエラーになる。</p>
<pre><code>Test-AsyncCmd: The WriteObject and WriteError methods cannot be called from outside the overrides of the BeginProcessing, ProcessRecord,
and EndProcessing methods, and they can only be called from within the same thread.
Validate that the cmdlet makes these calls correctly, or contact Microsoft Customer Support Services.
</code></pre>
<p>PowerShell の <code>WriteObject</code> は内部で「呼び出し元スレッド ID」を検証しており、Cmdlet 実行スレッド以外からの呼び出しを明確に禁止しているのだ。
出力系メソッドは、メインスレッドから呼び出さなくてはならない。</p>
<p>どうするか？</p>
<p>出力させたいものはキューに貯めて、後で出力する。</p>
<h2 id="section">修正案</h2>
<p>以下のようになる。</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System.Collections.Concurrent</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Diagnostics</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Management.Automation</span><span class="p">;</span>

<span class="p">[</span><span class="nf">Cmdlet</span><span class="p">(</span><span class="n">VerbsDiagnostic</span><span class="p">.</span><span class="n">Test</span><span class="p">,</span> <span class="s">"AsyncCmd"</span><span class="p">)]</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">TestAsyncCommand</span> <span class="p">:</span> <span class="n">PSCmdlet</span>
<span class="p">{</span>
    <span class="p">[</span><span class="nf">Parameter</span><span class="p">()]</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Message</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="p">=</span> <span class="s">"Hello"</span><span class="p">;</span>

    <span class="k">private</span> <span class="k">enum</span> <span class="n">RecordType</span> <span class="p">{</span> <span class="n">Output</span><span class="p">,</span> <span class="n">Error</span><span class="p">,</span> <span class="n">Warning</span><span class="p">,</span> <span class="n">Verbose</span><span class="p">,</span> <span class="n">Debug</span><span class="p">,</span> <span class="n">Information</span> <span class="p">}</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">record</span> <span class="k">struct</span> <span class="nc">OutputRecord</span><span class="p">(</span><span class="n">RecordType</span> <span class="n">Type</span><span class="p">,</span> <span class="kt">object</span> <span class="n">Value</span><span class="p">);</span>

    <span class="k">private</span> <span class="n">BlockingCollection</span><span class="p">&lt;</span><span class="n">OutputRecord</span><span class="p">&gt;</span> <span class="n">_outputs</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
    <span class="k">private</span> <span class="n">Task</span><span class="p">?</span> <span class="n">_outputTask</span><span class="p">;</span>
    <span class="k">private</span> <span class="n">Stopwatch</span> <span class="n">_sw</span> <span class="p">=</span> <span class="n">Stopwatch</span><span class="p">.</span><span class="nf">StartNew</span><span class="p">();</span>

    <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">BeginProcessing</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">_outputs</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">RecordType</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) Start: BeginProcessing"</span><span class="p">));</span>
        <span class="n">_outputTask</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="n">OutputAsync</span><span class="p">);</span>
        <span class="n">_outputs</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">RecordType</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) End: BeginProcessing"</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">EndProcessing</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">_outputs</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">RecordType</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) Start: EndProcessing"</span><span class="p">));</span>

        <span class="n">Task</span> <span class="n">_completedTask</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="p">=&gt;</span>
        <span class="p">{</span>
            <span class="k">try</span>
            <span class="p">{</span>
                <span class="k">await</span> <span class="n">_outputTask</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">finally</span>
            <span class="p">{</span>
                <span class="n">_outputs</span><span class="p">.</span><span class="nf">CompleteAdding</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">});</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">record</span> <span class="k">in</span> <span class="n">_outputs</span><span class="p">.</span><span class="nf">GetConsumingEnumerable</span><span class="p">())</span>
        <span class="p">{</span>
            <span class="k">switch</span> <span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Type</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Output</span><span class="p">:</span>
                    <span class="nf">WriteObject</span><span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span><span class="p">);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Error</span><span class="p">:</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span> <span class="k">is</span> <span class="n">ErrorRecord</span> <span class="n">err</span><span class="p">)</span>
                        <span class="nf">WriteError</span><span class="p">(</span><span class="n">err</span><span class="p">);</span>
                    <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span> <span class="k">is</span> <span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
                        <span class="nf">WriteError</span><span class="p">(</span><span class="k">new</span> <span class="nf">ErrorRecord</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span> <span class="s">"CmdletError"</span><span class="p">,</span> <span class="n">ErrorCategory</span><span class="p">.</span><span class="n">NotSpecified</span><span class="p">,</span> <span class="k">this</span><span class="p">));</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Warning</span><span class="p">:</span>
                    <span class="nf">WriteWarning</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Verbose</span><span class="p">:</span>
                    <span class="nf">WriteVerbose</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Debug</span><span class="p">:</span>
                    <span class="nf">WriteDebug</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Information</span><span class="p">:</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span> <span class="k">is</span> <span class="n">InformationRecord</span> <span class="n">info</span><span class="p">)</span>
                        <span class="nf">WriteInformation</span><span class="p">(</span><span class="n">info</span><span class="p">);</span>
                    <span class="k">else</span>
                        <span class="nf">WriteInformation</span><span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span><span class="p">,</span> <span class="p">[]);</span>
                    <span class="k">break</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">try</span>
        <span class="p">{</span>
            <span class="n">_completedTask</span><span class="p">.</span><span class="nf">Wait</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="k">catch</span> <span class="p">(</span><span class="n">AggregateException</span> <span class="n">ex</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="nf">WriteError</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="n">InnerException</span><span class="p">,</span> <span class="s">"AsyncError"</span><span class="p">,</span> <span class="n">ErrorCategory</span><span class="p">.</span><span class="n">NotSpecified</span><span class="p">,</span> <span class="k">this</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="k">finally</span>
        <span class="p">{</span>
            <span class="n">_outputs</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="nf">WriteVerbose</span><span class="p">(</span><span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) End: EndProcessing"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">async</span> <span class="n">Task</span> <span class="nf">OutputAsync</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">10</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
        <span class="p">{</span>
            <span class="n">_outputs</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="k">new</span> <span class="nf">OutputRecord</span><span class="p">(</span><span class="n">RecordType</span><span class="p">.</span><span class="n">Output</span><span class="p">,</span> <span class="s">$"(</span><span class="p">{</span><span class="n">_sw</span><span class="p">.</span><span class="n">Elapsed</span><span class="p">}</span><span class="s">) </span><span class="p">{</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">));</span>
            <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Delay</span><span class="p">(</span><span class="m">100</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="section-1">解説</h3>
<p>キューにはスレッドセーフな <code>BlockingCollection&lt;T&gt;</code> を使用するのが肝。</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">private</span> <span class="n">BlockingCollection</span><span class="p">&lt;</span><span class="n">OutputRecord</span><span class="p">&gt;</span> <span class="n">_outputs</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>
</code></pre></div></div>
<p>非同期メソッド内では、この <code>_output</code> に追加していく。</p>
<p>キューに貯める「生産者」を別スレッド、溜まったオブジェクトを使用する「消費者」を Cmdletのスレッドにするのだ。</p>
<p>スレッドセーフなキューとして、<code>ConcurrentQueue&lt;T&gt;</code> があるが、ただ <code>TryDequeue()</code> していくだけだと空になった後に追加で溜まるかもしれないものを待つのに別実装が必要になる。
対して <code>BlockingCollection&lt;T&gt;</code> は <code>GetConsumingEnumerable()</code> メソッドで生産完了フラグが立つまで待つことができるのだ。</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">EndProcessing</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="c1">// キュー生産者側を別スレッドで</span>
        <span class="n">Task</span> <span class="n">_completedTask</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="p">=&gt;</span>
        <span class="p">{</span>
            <span class="k">try</span>
            <span class="p">{</span>
                <span class="k">await</span> <span class="n">_outputTask</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">finally</span>
            <span class="p">{</span>
                <span class="n">_outputs</span><span class="p">.</span><span class="nf">CompleteAdding</span><span class="p">();</span>
            <span class="p">}</span>
        <span class="p">});</span>

        <span class="c1">// キューの消費者側は Cmdlet のスレッドで</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">record</span> <span class="k">in</span> <span class="n">_outputs</span><span class="p">.</span><span class="nf">GetConsumingEnumerable</span><span class="p">())</span>
        <span class="p">{</span>
            <span class="c1">// WriteObject() 等</span>
        <span class="p">}</span>

        <span class="c1">// 最後の終了処理</span>
        <span class="k">try</span>
        <span class="p">{</span>
            <span class="n">_completedTask</span><span class="p">.</span><span class="nf">Wait</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="k">catch</span> <span class="p">(</span><span class="n">AggregateException</span> <span class="n">ex</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="nf">WriteError</span><span class="p">(</span><span class="k">new</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="n">InnerException</span><span class="p">,</span> <span class="s">"AsyncError"</span><span class="p">,</span> <span class="n">ErrorCategory</span><span class="p">.</span><span class="n">NotSpecified</span><span class="p">,</span> <span class="k">this</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="k">finally</span>
        <span class="p">{</span>
            <span class="n">_outputs</span><span class="p">.</span><span class="nf">Dispose</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
</code></pre></div></div>
<p><code>_outputs.GetConsumingEnumerable()</code> 中は生産完了フラグが立つまで完全に待ちになるので、別スレッドから出力タスクの完了と <code>_outputs.CompleteAdding()</code> による生産完了通知をする必要がある点に注意。</p>
<p>また、溜め込むオブジェクトは <code>WriteObject</code>, <code>WriteError</code>, <code>WriteWarning</code> 等、どのストリームに出力するか区別できるようにしておくと、良い。</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">private</span> <span class="k">enum</span> <span class="n">RecordType</span> <span class="p">{</span> <span class="n">Output</span><span class="p">,</span> <span class="n">Error</span><span class="p">,</span> <span class="n">Warning</span><span class="p">,</span> <span class="n">Verbose</span><span class="p">,</span> <span class="n">Debug</span><span class="p">,</span> <span class="n">Information</span> <span class="p">}</span>
    <span class="k">private</span> <span class="k">readonly</span> <span class="n">record</span> <span class="k">struct</span> <span class="nc">OutputRecord</span><span class="p">(</span><span class="n">RecordType</span> <span class="n">Type</span><span class="p">,</span> <span class="kt">object</span> <span class="n">Value</span><span class="p">);</span>

</code></pre></div></div>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">record</span> <span class="k">in</span> <span class="n">_outputs</span><span class="p">.</span><span class="nf">GetConsumingEnumerable</span><span class="p">())</span>
        <span class="p">{</span>
            <span class="k">switch</span> <span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Type</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Output</span><span class="p">:</span>
                    <span class="nf">WriteObject</span><span class="p">(</span><span class="n">record</span><span class="p">.</span><span class="n">Value</span><span class="p">);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Error</span><span class="p">:</span>
                    <span class="nf">WriteError</span><span class="p">(...);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Warning</span><span class="p">:</span>
                    <span class="nf">WriteWarning</span><span class="p">(...);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Verbose</span><span class="p">:</span>
                    <span class="nf">WriteVerbose</span><span class="p">(...);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Debug</span><span class="p">:</span>
                    <span class="nf">WriteDebug</span><span class="p">(...);</span>
                    <span class="k">break</span><span class="p">;</span>
                <span class="k">case</span> <span class="n">RecordType</span><span class="p">.</span><span class="n">Information</span><span class="p">:</span>
                    <span class="nf">WriteInformation</span><span class="p">(...);</span>
                    <span class="k">break</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
</code></pre></div></div>
<hr />
<p>PowerShell の Cmdlet は本質的に同期的だが、
非同期処理を「Cmdlet スレッドに戻す」ことで非同期出力を実現できる。
<code>BlockingCollection&lt;T&gt;</code> を使ったこの方式は、PowerShell の制約を自然に回避する最も堅牢な方法のひとつだ。</p>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[PowerShell の Cmdlet で非同期処理に対応する方法。]]></summary></entry><entry><title type="html">Sashimi - PowerShellモジュール</title><link href="https://teramako.github.io/2026/08/13/Sashimi-PowerShell-module.html" rel="alternate" type="text/html" title="Sashimi - PowerShellモジュール" /><published>2026-08-13T23:00:00+09:00</published><updated>2026-08-13T23:00:00+09:00</updated><id>https://teramako.github.io/2026/08/13/Sashimi-PowerShell-module</id><content type="html" xml:base="https://teramako.github.io/2026/08/13/Sashimi-PowerShell-module.html"><![CDATA[<p>先日は<a href="https://teramako.github.io/2026/08/11/Sabamiso-PowerShell-module.html">Sabamiso.psm - PowerShell用外部コマンド補完モジュール</a>の紹介をした。
今日は <a href="https://github.com/teramako/Sashimi/" title="teramako/Sashimi: Raw process I/O for PowerShell — execute external commands and handle stdin/stdout/stderr as byte streams.">Sashimi</a> というPowerShellモジュールの紹介をするよ。
バージョン 3.0.0 をリリースし、当初計画してやろうと思ったことをすべてやったので、記念に。</p>
<img src="https://raw.githubusercontent.com/teramako/Sashimi/refs/heads/main/docs/img/Sashimi_512.png" alt="Sashimi logo" width="256" align="right">
<p><a href="https://github.com/teramako/Sashimi/" title="teramako/Sashimi: Raw process I/O for PowerShell — execute external commands and handle stdin/stdout/stderr as byte streams.">Sashimi</a> は外部コマンドの入出力をバイナリ(<code>byte[]</code>)で行うことにフォーカスしたコマンド群だ。</p>
<h2 id="section">動機</h2>
<h3 id="powershell-">PowerShell の外部コマンド実行における問題点</h3>
<p>PowerShell には内部にCmdletと呼ばれるコマンドや関数が用意されていて、基本的にはそれらを使用してコードを書くことになる。
他のシェルで言えばビルトイン・コマンドを多数抱えているようなものだ。
これらを使用している限り、PowerShellの特徴であるオブジェクト・ベースのパイプライン入出力が可能になる。</p>
<p>とはいえ、外部コマンドも必要になるし、実行も当然できるようになっている。
できるのだが、入出力に一定の制限が掛かる。
パイプライン入出力は <strong>文字列</strong> になる制限だ。</p>
<p>正確には全てが文字列になるわけではなく、<code>外部コマンド1 | 外部コマンド2</code> と外部コマンドが連結されている場合、この間はバイナリで入出力が行われるのだがPowerShell上からは見えない内部に隠ぺいされた動きなので、全て文字列になると思って良い。</p>
<p>多くの場合はこれで問題ない。
だが、文字列では困る場合もある。</p>
<ul>
<li>バイナリデータそのものが欲しい</li>
<li>入出力で扱われる文字コードが、文字列化時のエンコーディングと合っていない</li>
</ul>
<p>1番目の「バイナリデータそのものが欲しい」はまぁ稀だろうけど、2番目のは割と遭遇したことがあるのではないだろうか？</p>
<h3 id="section-1">解決策</h3>
<p>いちおう解決策はある。</p>
<p>外部コマンドを実行して、その出力が文字列化される時のエンコーディングをあらかじめ変更しておくことだ。
<code>[Console]::OutputEncoding</code> をコマンドが出力するエンコーディングに変えることで対応が可能となる。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">try</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$currentEncoding</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">Console</span><span class="p">]::</span><span class="n">OutputEncoding</span><span class="w">
    </span><span class="p">[</span><span class="n">Console</span><span class="p">]::</span><span class="nx">OutputEncoding</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.Text.Encoding</span><span class="p">]::</span><span class="n">UTF8</span><span class="w">
    </span><span class="nv">$result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">externalCommand</span><span class="w"> </span><span class="c"># ←外部コマンド</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">finally</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="p">[</span><span class="n">Console</span><span class="p">]::</span><span class="n">OutputEncoding</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$currentEncoding</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>一応ね、これで解決はするんだけどさ、毎回こんな面倒なことやってられない。</p>
<p>というのが開発のきっかけ。</p>
<h2 id="quotsashimiquot-">&quot;Sashimi&quot; について</h2>
<p>プロジェクト名/モジュール名はあっさりと決まった。</p>
<ul>
<li>プロジェクト名には「食べ物」とか「料理」から引っ張ってくるのが個人的な流行</li>
<li>文字列ではなく、バイナリデータ（<strong>生データ</strong>）を扱う</li>
</ul>
<p>⇒ 刺身！</p>
<h2 id="section-2">インストール</h2>
<p>PowerShell Gallery で公開している。</p>
<p><strong>PowerShell 7.6</strong> 以上が必要な点に注意！</p>
<ul>
<li><a href="https://www.powershellgallery.com/packages/Sashimi/3.0.0">PowerShell Gallery | Sashimi 3.0.0</a></li>
</ul>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-PSResource</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Sashimi</span><span class="w">
</span></code></pre></div></div>
<h2 id="section-3">コマンド紹介</h2>
<h3 id="invoke-rawcommand-alias-raw">Invoke-RawCommand (alias: raw)</h3>
<p>主題のコマンド。</p>
<p>外部コマンドを実行し、標準入力/標準出力/標準エラー を <code>byte[]</code> で受け取るコマンド。</p>
<ul>
<li>ドキュメント: <a href="https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Invoke-RawCommand.md">https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Invoke-RawCommand.md</a></li>
</ul>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">Invoke-RawCommand</span><span class="w"> </span><span class="nx">echo</span><span class="w"> </span><span class="nx">abc</span><span class="w">
</span><span class="mi">97</span><span class="w">
</span><span class="mi">98</span><span class="w">
</span><span class="mi">99</span><span class="w">
</span><span class="mi">10</span><span class="w">
</span><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="s2">"うんこ"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Invoke-RawCommand</span><span class="w"> </span><span class="nx">cat</span><span class="w">
</span><span class="mi">227</span><span class="w">
</span><span class="mi">129</span><span class="w">
</span><span class="mi">134</span><span class="w">
</span><span class="mi">227</span><span class="w">
</span><span class="mi">130</span><span class="w">
</span><span class="mi">147</span><span class="w">
</span><span class="mi">227</span><span class="w">
</span><span class="mi">129</span><span class="w">
</span><span class="mi">147</span><span class="w">
</span></code></pre></div></div>
<p>生データを扱うことが主題としてあるので、基本的には <code>byte[]</code> が出力されて上記のような結果になる。</p>
<p>さすがにこれでは非常に扱い難いｗ</p>
<p>ということで、<code>-AsString</code> パラメーター（alias: <code>-s</code>）と <code>-Encoding</code> パラメーター（alias: <code>-e</code>）がある。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">Invoke-RawCommand</span><span class="w"> </span><span class="nt">-s</span><span class="w"> </span><span class="nx">echo</span><span class="w"> </span><span class="nx">abc</span><span class="w">
</span><span class="n">abc</span><span class="w">
</span><span class="nx">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="s2">"うんこ"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Invoke-RawCommand</span><span class="w"> </span><span class="nt">-s</span><span class="w"> </span><span class="nx">cat</span><span class="w">
</span><span class="err">うんこ</span><span class="w">
</span></code></pre></div></div>
<p>ちなみに、<code>-AsString</code> による文字列化だが、 <strong>行単位</strong> で出力する仕様。</p>
<p><code>-Encoding</code> はデフォルト(未指定)では UTF-8 になる。Shift_JIS出力されるものを扱いたい場合は <code>-Encoding</code> を指定する。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">raw</span><span class="w"> </span><span class="nt">-s</span><span class="w"> </span><span class="o">.</span><span class="nx">\test-shift_jis.bat</span><span class="w">
</span><span class="err">�����</span><span class="n">Shift_JIS</span><span class="err">����</span><span class="w">
</span><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">raw</span><span class="w"> </span><span class="nt">-s</span><span class="w"> </span><span class="nt">-e</span><span class="w"> </span><span class="nx">shift_jis</span><span class="w"> </span><span class="o">.</span><span class="nx">\test-shift_jis.bat</span><span class="w">
</span><span class="err">これは</span><span class="n">Shift_JIS</span><span class="err">だよ</span><span class="w">
</span></code></pre></div></div>
<h4 id="section-4">標準エラー出力</h4>
<p><code>-AsString</code> は基本的に標準出力に作用するパラメーターだった。
対して、標準エラーは、<code>-AsString</code> の有無に関わらず文字列化する。</p>
<p>標準出力はバイナリになる可能性があるが、標準エラーは基本的にコンソールに印字されるべきものであり文字列だろう。という前提。</p>
<h4 id="section-5">コマンド引数を正しく与える</h4>
<p>以下を実行した時、どうなるか？</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"abc</span><span class="se">`n</span><span class="s2">"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">raw</span><span class="w"> </span><span class="nt">-s</span><span class="w"> </span><span class="nx">cat</span><span class="w"> </span><span class="nt">-e</span><span class="w">
</span></code></pre></div></div>
<p>答え:
エラーになる。</p>
<pre><code class="language-output">Invoke-RawCommand: Missing an argument for parameter 'Encoding'. Specify a parameter of type 'System.String' and try again.
</code></pre>
<p><code>-e</code> が <code>Invoke-RawCommand</code> のパラメーターと解釈されてしまうためだ。</p>
<p>正しく cat コマンドに渡すには:</p>
<ul>
<li><code>&quot;abc&quot; | raw -s cat '-e'</code> と引用符で囲って文字列であると明示する</li>
<li><code>&quot;abc&quot; | raw -s cat -- -e</code> と <code>--</code> を付ける</li>
</ul>
<p>少々面倒だが、我慢してもらうしかない。</p>
<h4 id="scriptblock">ScriptBlockモード</h4>
<p>上記のような課題と、<code>raw ... | raw ... | raw ...</code> と毎度 <code>raw</code> を付けるのが面倒。
ということで ScriptBlock モードを用意している。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">raw</span><span class="w"> </span><span class="nt">-s</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"abc</span><span class="se">`n</span><span class="s2">"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">cat</span><span class="w"> </span><span class="nt">-e</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">base64</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><code>{ ... }</code> にPowerShellスクリプトを書くことができる。
(PowerShell 的に由緒正しいコードで、<code>ScriptBlock</code> という匿名関数に近いもの)</p>
<p>実はブロック内を AST 解析をして、外部コマンド部分と判断できた部分を書き換えて実行しており、
ただの1ステートメントに限らず複数のステートメントを書けるし、<code>if</code> や <code>foreach</code> 文を書くこともできる。変数も読み書きできる。</p>
<p>また、外部コマンドがパイプで連続している場合には、<code>-AsString</code>(<code>-s</code>)を末尾のみに適用させる工夫もしている。</p>
<p>よって、<code>curl</code> で画像(バイナリデータ)を取ってきて、<code>img2sixel</code> でSixelに変換し、文字列として出力するようなことができる。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$imgUrl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'https://raw.githubusercontent.com/teramako/Sashimi/refs/heads/main/docs/img/Sashimi_512.png'</span><span class="w">
</span><span class="n">raw</span><span class="w"> </span><span class="nt">-s</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">curl</span><span class="w"> </span><span class="nv">$imgUrl</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">img2sixel</span><span class="w"> </span><span class="nt">-w256</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><img src="/img/2026-08-13/curl-and-img2sixel.png" alt="curl and img2sixel" /></p>
<h3 id="convertto-rawstring-alias-b2a">ConvertTo-RawString (alias: b2a)</h3>
<p><code>byte[]</code> → <code>string</code> を行う補助コマンド。 (&quot;binary to ascii&quot; 略して &quot;b2a&quot;)</p>
<ul>
<li>ドキュメント: <a href="https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/ConvertTo-RawString.md">https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/ConvertTo-RawString.md</a></li>
</ul>
<p><code>iconv -t utf8 -f ...</code> と思ってくれて良い。</p>
<p>元々 <code>Invoke-RawCommand</code> に <code>-AsString</code> パラメーターがない時のもので、
<code>raw ... | raw ... | b2a -e shift_jis</code> みたいな使い方をして最終的に文字列を得ることを目的としていた。<code>-AsString</code> がある今は、このコマンドの存在意義は薄れた。</p>
<p>ただ、<code>ConvertTo-RawString</code> には <code>-Raw</code> パラメーターがある。
<code>Invoke-RawCommand</code> の <code>-AsString</code> と <code>ConvertTo-RawString</code> (<code>-Raw</code>なし)は行単位で出力し、改行コードは除去されるので、これでは不都合がある場合には使うことがあるだろう。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">raw</span><span class="w"> </span><span class="nx">cat</span><span class="w"> </span><span class="nx">test.json</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">b2a</span><span class="w"> </span><span class="nt">-Raw</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ConvertFrom-Json</span><span class="w">
</span></code></pre></div></div>
<h3 id="convertfrom-rawstring-alias-a2b">ConvertFrom-RawString (alias: a2b)</h3>
<p><code>string</code> → <code>byte[]</code> を行う補助コマンド。 (&quot;ascii to binary&quot; 略して &quot;a2b&quot;)</p>
<ul>
<li>ドキュメント: <a href="https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/ConvertFrom-RawString.md">https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/ConvertFrom-RawString.md</a></li>
</ul>
<p><code>iconv -f utf8 -t ...</code> と思ってくれて良い。</p>
<p><code>Invoke-RawCommand</code> の標準入力に <code>byte[]</code> のみを受け付けていた時のもので、
<code>&quot;うんこ&quot; | a2b | raw ...</code> みたいな使い方を目的としていた。</p>
<h3 id="out-rawfile-alias-rawout">Out-RawFile (alias: rawout)</h3>
<p><code>byte[]</code> → ファイル を行う補助コマンド。</p>
<ul>
<li>ドキュメント: <a href="https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Out-RawFile.md">https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Out-RawFile.md</a></li>
</ul>
<p>PowerShellのリダイレクトや <code>Out-File</code> って、<code>byte[]</code> データをバイナリとして書き出してくれないので作った。</p>
<h3 id="show-hexdump-alias-hexd">Show-HexDump (alias: hexd)</h3>
<p>Unix系コマンドで言えば、<code>hexdump</code> コマンドにあたる。</p>
<ul>
<li>ドキュメント: <a href="https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Show-HexDump.md">https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Show-HexDump.md</a></li>
</ul>
<p>元々はこれ単体で <a href="https://github.com/teramako/HexDump/">https://github.com/teramako/HexDump/</a> プロジェクトとして作っていたのだが、<a href="https://github.com/teramako/Sashimi/" title="teramako/Sashimi: Raw process I/O for PowerShell — execute external commands and handle stdin/stdout/stderr as byte streams.">Sashimi</a> と非常に相性が良いので取り込んだ。</p>
<p>マルチバイトの表示には結構こだわって作っていて絵文字のようなサロゲートペアとなるような文字も奇麗に出せる。</p>
<h4 id="section-6">サンプル画像</h4>
<p><a href="https://github.com/teramako/HexDump/">旧HexDumpプロジェクト</a>時に撮ったものなので若干古いけど、</p>
<ul>
<li><img src="https://github.com/user-attachments/assets/01182654-a0ce-49bb-a51b-5a6b64094bb9" alt="" /></li>
<li><img src="https://github.com/user-attachments/assets/29a41c77-fda2-461a-ba1e-710716e0e63f" alt="" /></li>
<li><img src="https://github.com/user-attachments/assets/3168ba4e-4ee5-4df3-be4e-5ddefb0d8e8d" alt="" /></li>
</ul>
<p>カラーリングも行える</p>
<ul>
<li><img src="https://github.com/user-attachments/assets/bfa54c04-a758-4ee0-9dca-a5884a799d58" alt="" /></li>
<li><img src="https://github.com/user-attachments/assets/464e23b4-6f24-45d5-80c2-ee9a3d7b5361" alt="" /></li>
<li><img src="https://github.com/user-attachments/assets/ba7c7a7b-95c1-4eb3-9995-a3b2b75f8b42" alt="" /></li>
</ul>
<h3 id="test-rawcommand-alias-raw">Test-RawCommand (alias: raw?)</h3>
<ul>
<li>ドキュメント: <a href="https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Test-RawCommand.md">https://github.com/teramako/Sashimi/blob/main/docs/Sashimi/en-US/Test-RawCommand.md</a></li>
</ul>
<p><code>Invoke-RawCommand</code> と内部でやっていることはほぼ同じだが、外部コマンドを実行したプロセスの終了コードによって真偽値（終了コードが 0 なら <code>$true</code>, それ以外は <code>$false</code>）を返すことを目的としている。</p>
<p>出力結果ではなく、終了コードによる条件分岐をしたい場面での使用を想定している。</p>
<p>標準出力、標準エラーは Information レコードとして出力をするので、後々結果を取り出すことも可能だ。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="n">raw</span><span class="nf">?</span><span class="w"> </span><span class="nx">grep</span><span class="w"> </span><span class="nx">pattern</span><span class="w"> </span><span class="nx">path/to/text</span><span class="w"> </span><span class="nt">-InformationVariable</span><span class="w"> </span><span class="nx">info</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$info</span><span class="o">.</span><span class="nf">Where</span><span class="p">({</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Tags</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="s2">"stdout"</span><span class="p">})</span><span class="o">.</span><span class="nf">Message</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Write-Output</span><span class="w">
</span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$info</span><span class="o">.</span><span class="nf">Where</span><span class="p">({</span><span class="bp">$_</span><span class="o">.</span><span class="nf">Tags</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="s2">"stderr"</span><span class="p">})</span><span class="o">.</span><span class="nf">Message</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Write-Error</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[先日はSabamiso.psm - PowerShell用外部コマンド補完モジュールの紹介をした。 今日は Sashimi というPowerShellモジュールの紹介をするよ。 バージョン 3.0.0 をリリースし、当初計画してやろうと思ったことをすべてやったので、記念に。]]></summary></entry><entry><title type="html">Sabamiso.psm - PowerShell用外部コマンド補完モジュール</title><link href="https://teramako.github.io/2026/08/11/Sabamiso-PowerShell-module.html" rel="alternate" type="text/html" title="Sabamiso.psm - PowerShell用外部コマンド補完モジュール" /><published>2026-08-11T19:00:00+09:00</published><updated>2026-08-11T19:00:00+09:00</updated><id>https://teramako.github.io/2026/08/11/Sabamiso-PowerShell-module</id><content type="html" xml:base="https://teramako.github.io/2026/08/11/Sabamiso-PowerShell-module.html"><![CDATA[<p>去年(2025年)末に <a href="/2025/12/16/PowerShell-NativeCommandCompletion">PowerShell で外部コマンドの補完</a> で PowerShell 7.6 から <a href="https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/register-argumentcompleter?view=powershell-7.6" title="Register-ArgumentCompleter (Microsoft.PowerShell.Core) - PowerShell | Microsoft Learn">Register-ArgumentCompleter</a> に <code>-NativeFallback</code> のパラメーターがされてコマンドライン補完のコードを包括的に行えるようになったことを書いた。</p>
<p>Powershell モジュールとして動くものができているので、ずいぶんを遅くなってしまったが、その報告。</p>
<ul>
<li><a href="https://github.com/teramako/Sabamiso.psm" title="teramako/Sabamiso.psm: PowerShell module for complete native command parameters and arguments">Sabamiso.psm</a> (<a href="https://www.powershellgallery.com/packages/Sabamiso.psm/">PowerShell Gallery | Sabamiso.psm</a>)</li>
<li><a href="https://github.com/teramako/Sabamiso.completions" title="teramako/Sabamiso.completions: Completios definitions for Sabamiso.psm">Sabamiso.completions</a> (<a href="https://www.powershellgallery.com/packages/Sabamiso.completions/">PowerShell Gallery | Sabamiso.completions</a>)</li>
</ul>
<p>以前は &quot;NativeCommandCompleter.psm&quot; というド直球な名前だったが改名した。
また、補完エンジンとなるコアのモジュールである <a href="https://github.com/teramako/Sabamiso.psm" title="teramako/Sabamiso.psm: PowerShell module for complete native command parameters and arguments">Sabamiso.psm</a> と、各コマンドの補完コードを集めた <a href="https://github.com/teramako/Sabamiso.completions" title="teramako/Sabamiso.completions: Completios definitions for Sabamiso.psm">Sabamiso.completions</a> に分離している。</p>
<h2 id="a-hrefhttpsgithubcomteramakosabamisopsm-titleteramakosabamisopsm-powershell-module-for-complete-native-command-parameters-and-argumentssabamisopsma"><a href="https://github.com/teramako/Sabamiso.psm" title="teramako/Sabamiso.psm: PowerShell module for complete native command parameters and arguments">Sabamiso.psm</a></h2>
<p>補完エンジンと補完コードの登録を行うコマンド群を提供するモジュールである。</p>
<img src="https://raw.githubusercontent.com/teramako/Sabamiso.psm/refs/heads/main/docs/imgs/Sabamiso_x512.png" width="256" align="right">
<p>大枠の仕組みは <a href="/2025/12/16/PowerShell-NativeCommandCompletion">PowerShell で外部コマンドの補完</a> に書いた時と変わっていない。</p>
<ol>
<li>補完開始</li>
<li>コマンドライン解析</li>
<li>コマンド指定がある
<ul>
<li>PowerShell コマンド(Cmdlet)の補完 (PowerShell本体の仕事)</li>
<li>コマンド名指定で補完コードが登録されている(<code>Register-ArgumentCompleter -CommandName ...</code> ) → その補完(登録されたスクリプト実行)</li>
<li>上記2つに該当しない (← <strong>本 Sabamiso.psm の仕事</strong>)
<ul>
<li>実行コマンド名を取得、コマンド名の補完定義が登録済み(キャッシュ済み)か確認</li>
<li>登録済み
<ul>
<li>該当の補完定義から補完を開始</li>
</ul>
</li>
<li>未登録
<ul>
<li>環境変数 <code>PS_COMPLETE_PATH</code> にある各ディレクトリ(環境変数 <code>PS_COMPLETE_PATH</code> は未設定なら、<code>&lt;$PROFILE のあるディレクトリ&gt;/completions</code> が設定される)から
<code>実行コマンド名.ps1</code> のスクリプトを探す。</li>
<li>見つかったスクリプトを実行。</li>
<li>再度補完定義が登録されたか確認。(登録されていれば、「登録済み」へ)</li>
</ul>
</li>
</ul>
</li>
<li>上記を通して補完候補がない
<ul>
<li>ファイル・ディレクトリ名補完 (PowerShell本体の仕事)</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>上記のような仕組みで動いている。</p>
<p>bash, zsh, fish 等のUnix系シェルの <code>/usr/share/*/completions/</code>, <code>/etc/*/completions/</code> を真似していると言えば分かりやすいだろうか。</p>
<h3 id="section">遅延読み込み</h3>
<p>上記のような仕組みで補完発動時に動的に読むため、PowerShell起動時に各コマンド用の補完コードを読み込む必要がなくなり、起動時間が長くなる要因を削減できる。</p>
<p>また、後述するけれど <code>posh-git</code> のような既存の補完モジュールを再利用するかたちでの遅延読み込みも可能。</p>
<h3 id="menu-complete">補完候補出力(menu-complete)には、その説明文が表示される</h3>
<p>fish の補完で候補と共に説明が出てくるのが好きで、強くインスパイアしている。</p>
<p><img src="/img/2026-08-11/complete-ls-parameters.png" alt="Complete ls parameters" /></p>
<h3 id="windows">Windows対応</h3>
<p>Windows コマンドで <code>/</code> 始まりのパラメーターも補完できる。</p>
<p><img src="/img/2026-08-11/complete-whoami-on-windows.png" alt="Complete whoami parameters" /></p>
<p>また、上図のようにロケールに合わせてローカライズされた説明文も出せるようになっている。</p>
<h3 id="section-1">特殊な形態のコマンド</h3>
<p>少し工夫が必要だが、 <code>dd</code> コマンドのような <code>-</code> から始まらないような特殊なパラメーターにも対応できる。
<img src="/img/2026-08-11/complete-dd-parameters.png" alt="Complete dd parameters" /></p>
<h2 id="a-hrefhttpsgithubcomteramakosabamisocompletions-titleteramakosabamisocompletions-completios-definitions-for-sabamisopsmsabamisocompletionsa"><a href="https://github.com/teramako/Sabamiso.completions" title="teramako/Sabamiso.completions: Completios definitions for Sabamiso.psm">Sabamiso.completions</a></h2>
<p>私自身が使うために、かつ、サンプルとして定義している補完コード群。</p>
<h2 id="section-2">補完コードのスクリプト</h2>
<p>実際の補完コードがどんな風になるかを書いておこう。</p>
<p>私自身が使うために書いているコードも参考に: <a href="https://github.com/teramako/Sabamiso.completions/tree/main/completions">https://github.com/teramako/Sabamiso.completions/tree/main/completions</a></p>
<p>補完コードの役割は <strong>「補完定義」を登録</strong> することだ。
そのコマンドが <a href="https://github.com/teramako/Sabamiso.psm/blob/main/docs/Sabamiso.psm/Register-NativeCompleter.md">Register-NativeCompleter</a> になる。</p>
<p>一度登録されると、解除されない限りそのスクリプトは実行されないことに注意。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Register-NativeCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"&lt;コマンド名&gt;"</span><span class="w"> </span><span class="se">`
</span><span class="w">                         </span><span class="nt">-Description</span><span class="w"> </span><span class="s2">"&lt;コマンドの説明&gt;"</span><span class="w"> </span><span class="se">`
</span><span class="w">                         </span><span class="nt">-Aliases</span><span class="w"> </span><span class="p">@(</span><span class="err">別名</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                         </span><span class="nt">-Parameters</span><span class="w"> </span><span class="p">@(</span><span class="err">パラメーター定義</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                         </span><span class="nt">-SubCommands</span><span class="w"> </span><span class="p">@(</span><span class="err">サブコマンド定義</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                         </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@(</span><span class="err">コマンド引数定義</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                         </span><span class="nt">-Style</span><span class="w"> </span><span class="s1">'スタイル'</span><span class="w"> </span><span class="se">`
</span><span class="w">                         </span><span class="nt">-NoFileCompletions</span><span class="w">
</span></code></pre></div></div>
<p>このコマンドで、定義と登録ができる。
定義のみを作る <a href="https://github.com/teramako/Sabamiso.psm/blob/main/docs/Sabamiso.psm/New-CommandCompleter.md">New-CommandCompleter</a> もあるが、こちらは SubCommands のところで説明する。</p>
<ul>
<li><code>-Name</code>: コマンド名。必須</li>
<li><code>-Description</code>: そのコマンドの説明。オプショナル</li>
<li><code>-Aliases</code>: 主にサブコマンド用。(<code>tmux list-sessions</code> = <code>tmux ls</code> のような別名を定義するもの)</li>
<li><code>-Parameters</code>: <code>-i</code> や <code>--ignore-case</code> 等のオプション定義群</li>
<li><code>-SubCommands</code>: <code>git branch ...</code> の <code>branch</code> 等のサブコマンド群</li>
<li><code>-Arguments</code>: コマンド引数の補完定義</li>
<li><code>-NoFileCompletions</code>: 引数の補完時、ファイルパスの補完を無効にする</li>
<li><code>-Style</code>: コマンドのスタイル (<code>GNU</code>(デフォルト), <code>Unix</code>, <code>Windows</code>)
<ul>
<li><code>GNU</code>: GNU系コマンドのスタイル。</li>
<li><code>Unix</code>: <code>GNU</code>とほぼ同じだが、<code>--&lt;ロングオプション&gt;=&lt;値&gt;</code> の <code>=</code>区切り指定はできない</li>
<li><code>Windows</code>: オプション接頭辞が <code>/</code> になり、引数指定も <code>/&lt;オプション名&gt;:&lt;値&gt;</code> と <code>:</code> 区切りになる</li>
</ul>
</li>
</ul>
<h3 id="parameters">Parameters</h3>
<p>パラメーターの定義は <a href="https://github.com/teramako/Sabamiso.psm/blob/main/docs/Sabamiso.psm/New-ParamCompleter.md">New-ParamCompleter</a> で行う。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="nt">-ShortName</span><span class="w"> </span><span class="p">@(</span><span class="err">ショートオプション名</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                   </span><span class="nt">-StandardName</span><span class="w"> </span><span class="p">@(</span><span class="err">通常オプション名</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                   </span><span class="nt">-LongName</span><span class="w"> </span><span class="p">@(</span><span class="err">ロングオプション名</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                   </span><span class="nt">-Description</span><span class="w"> </span><span class="s2">"説明"</span><span class="w"> </span><span class="se">`
</span><span class="w">                   </span><span class="nt">-Arguments</span><span class="w"> </span><span class="err">&lt;引数補完定義&gt;</span><span class="w"> </span><span class="se">`
</span><span class="w">                   </span><span class="nt">-Style</span><span class="w"> </span><span class="err">&lt;スタイル&gt;</span><span class="w">
</span></code></pre></div></div>
<ul>
<li><code>-ShortName</code>: Unix系コマンドのショートオプション。
<ul>
<li>一文字であること。</li>
<li><code>ls -lF</code> の <code>lF</code> のように複数組わせられる。</li>
<li>引数を必要する場合、 <code>head -n10</code> のように、<code>&lt;ショートオプション&gt;&lt;値&gt;</code> の入力が可能</li>
<li>規定の接頭辞は <code>-</code>。</li>
</ul>
</li>
<li><code>-StandardName</code>: 旧スタイルのUnix系コマンド(<code>find</code> コマンドとか)、または、Windowsによくあるオプション。
<ul>
<li>規定の接頭辞は <code>-</code>。</li>
</ul>
</li>
<li><code>-LongName</code>: いわゆるGNUロングオプション
<ul>
<li>規定の接頭辞は <code>--</code>。</li>
<li>引数を必要とする場合、 <code>--file=...</code>, <code>--file ...</code> と <code>=</code> または <code> </code> の区切りが可能
<ul>
<li><code>grep --color=&lt;when&gt;</code> のような引数が無い場合にフラグとして作用するものの場合は、<code>=</code> のみが区切り</li>
</ul>
</li>
</ul>
</li>
<li><code>-Arguments</code>: 引数の定義
<ul>
<li>省略すると、フラグのパラメーターとして作用する</li>
<li>引数値をファイルパスなどの典型的な型、静的な候補リスト、動的にPowerShellスクリプトから補完する設定が可能</li>
</ul>
</li>
<li><code>-Style</code>: パラメーター固有のスタイル
<ul>
<li>基本的には親コマンド定義の<code>Style</code>を引き継ぐが、特殊な事情がある場合に指定。通常は使わない。</li>
</ul>
</li>
</ul>
<h4 id="section-3">例</h4>
<p><code>grep</code> コマンドから一部抜粋。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Register-NativeCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">grep</span><span class="w"> </span><span class="nt">-Parameters</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">i</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">ignore-case</span><span class="w"> </span><span class="err">-Description</span><span class="w"> </span><span class="s1">'Ignore case'</span><span class="w">
    </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">f</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">file</span><span class="w"> </span><span class="err">-Description</span><span class="w"> </span><span class="s1">'Use patterns from a file'</span><span class="w"> </span><span class="err">`</span><span class="w">
                       </span><span class="err">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'FILE'</span><span class="p">;</span><span class="w"> </span><span class="nx">Type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'File'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">color</span><span class="w"> </span><span class="err">`</span><span class="w">
                       </span><span class="err">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'WHEN'</span><span class="p">;</span><span class="w"> </span><span class="nx">Nargs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'?'</span><span class="p">;</span><span class="w"> </span><span class="nx">Candidates</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"auto"</span><span class="p">,</span><span class="s2">"always"</span><span class="p">,</span><span class="s2">"never"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>
<h3 id="subcommands">SubCommands</h3>
<p>サブコマンドの定義は、通常のコマンド定義と同じ <a href="https://github.com/teramako/Sabamiso.psm/blob/main/docs/Sabamiso.psm/New-CommandCompleter.md">New-CommandCompleter</a> で行う。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">New-CommandCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"&lt;サブコマンド名&gt;"</span><span class="w"> </span><span class="se">`
</span><span class="w">                     </span><span class="nt">-Description</span><span class="w"> </span><span class="s2">"&lt;コマンドの説明&gt;"</span><span class="w"> </span><span class="se">`
</span><span class="w">                     </span><span class="nt">-Aliases</span><span class="w"> </span><span class="p">@(</span><span class="err">別名</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                     </span><span class="nt">-Parameters</span><span class="w"> </span><span class="p">@(</span><span class="err">パラメーター定義</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                     </span><span class="nt">-SubCommands</span><span class="w"> </span><span class="p">@(</span><span class="err">サブコマンド定義</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                     </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@(</span><span class="err">コマンド引数定義</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">                     </span><span class="nt">-Style</span><span class="w"> </span><span class="s1">'スタイル'</span><span class="w"> </span><span class="se">`
</span><span class="w">                     </span><span class="nt">-NoFileCompletions</span><span class="w">
</span></code></pre></div></div>
<p>パラメーターは <code>Register-NativeCompleter</code> と同じ。</p>
<h4 id="section-4">例</h4>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Register-NativeCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">git</span><span class="w"> </span><span class="nt">-SubCommands</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="err">New-CommandCompleter</span><span class="w"> </span><span class="err">-Name</span><span class="w"> </span><span class="err">log</span><span class="w"> </span><span class="err">-Parameters</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">g</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">graph</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">p</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">patch</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">n</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">max-count</span><span class="w"> </span><span class="err">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'NUM'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="s1">'stat'</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="s1">'since'</span><span class="p">,</span><span class="s1">'after'</span><span class="w"> </span><span class="err">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'date'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="s1">'until'</span><span class="p">,</span><span class="s1">'before'</span><span class="w"> </span><span class="err">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'date'</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="s1">'author'</span><span class="p">,</span><span class="s1">'committer'</span><span class="w"> </span><span class="err">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'pattern'</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="p">)</span><span class="w">
    </span><span class="err">New-CommandCompleter</span><span class="w"> </span><span class="err">-Name</span><span class="w"> </span><span class="err">add</span><span class="w"> </span><span class="err">-Parameters</span><span class="w"> </span><span class="p">@(</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">n</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">dry-run</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">v</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">verbose</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">f</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">force</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">p</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">patch</span><span class="w">
        </span><span class="err">New-ParamCompleter</span><span class="w"> </span><span class="err">-ShortName</span><span class="w"> </span><span class="err">A</span><span class="w"> </span><span class="err">-LongName</span><span class="w"> </span><span class="err">all</span><span class="w">
    </span><span class="p">)</span><span class="w">
</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>
<h3 id="arguments">Arguments</h3>
<p>引数の補完定義(<code>Register-ArgumentCompleter</code> や <code>New-ParamCompleter</code> の <code>-Arguments</code> パラメーターの記述方法)は、やり方が2通りある。</p>
<ul>
<li><a href="https://github.com/teramako/Sabamiso.psm/blob/main/docs/Sabamiso.psm/New-ArgumentCompleter.md">New-ArgumentCompleter</a> コマンドを使用する</li>
<li>ただのハッシュテーブル(<code>@{ ...}</code>)を使用する</li>
</ul>
<p>どちらを使用しても良いが、コマンドを使用すると LanguageServer の支援を受けやすいが、冗長になりがちだ。
個人的な使い分けは、<code>-Arguments</code> に直接書く時はハッシュテーブルで簡潔に。使いまわすために変数に入れる時はコマンドとしている。</p>
<p>定義だが、補完候補を生成しないものと生成するものに3種類の合計4種類ある。</p>
<h4 id="section-5">補完候補を生成しないもの</h4>
<p>ただ引数が必要だと示すだけのもの。
特に引数が必須のオプションは <code>-Arguments</code> の指定を必須としているため、<code>@{ Name = 'value名' }</code> を各機会は多い。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">(</span><span class="n">New-ArgumentCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">NUM</span><span class="p">)</span><span class="w">
</span><span class="c"># or</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'NUM'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h4 id="section-6">補完候補生成: ファイル/ディレクトリやコマンド補完</h4>
<p>ファイル/ディレクトリのパスやコマンドを補完させるもの。<code>-Type</code> パラメーターを指定する。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">(</span><span class="n">New-ArgumentCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">file</span><span class="w"> </span><span class="nt">-Type</span><span class="w"> </span><span class="nx">File</span><span class="p">)</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">(</span><span class="n">New-ArgumentCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">dir</span><span class="w"> </span><span class="nt">-Type</span><span class="w"> </span><span class="nx">Directory</span><span class="p">)</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">(</span><span class="n">New-ArgumentCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">cmd</span><span class="w"> </span><span class="nt">-Type</span><span class="w"> </span><span class="nx">Command</span><span class="p">)</span><span class="w">
</span><span class="c"># or</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'file'</span><span class="p">;</span><span class="w"> </span><span class="nx">Type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'File'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'dir'</span><span class="p">;</span><span class="w"> </span><span class="nx">Type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Directory'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'cmd'</span><span class="p">;</span><span class="w"> </span><span class="nx">Type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Command'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>他にも <code>DelegatingCommand</code> という特殊なものがある。(<code>sudo</code> や <code>time</code> のように引数がコマンドで、それ以降はそのコマンドの補完が必要となるケース用)</p>
<h4 id="section-7">補完候補生成: 静的なリスト</h4>
<p>事前に候補が分かっている場合は、<code>-Candidates</code> パラメーターに列挙する。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">(</span><span class="n">New-ArgumentCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">color</span><span class="w"> </span><span class="nt">-Candidates</span><span class="w"> </span><span class="s1">'always'</span><span class="p">,</span><span class="s1">'auto'</span><span class="p">,</span><span class="s1">'never'</span><span class="p">)</span><span class="w">
</span><span class="c"># or</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'color'</span><span class="p">;</span><span class="w"> </span><span class="nx">Candidates</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'always'</span><span class="p">,</span><span class="s1">'auto'</span><span class="p">,</span><span class="s1">'never'</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h4 id="section-8">補完候補生成: 動的なスクリプト</h4>
<p>PowerShell スクリプト(ScriptBlock) で補完候補を生成には <code>-Script</code> パラメーターを使用する。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">(</span><span class="w">
    </span><span class="n">New-ArgumentCompleter</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">GROUP</span><span class="w"> </span><span class="nt">-Script</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$wordToComplete</span><span class="p">)</span><span class="w">
        </span><span class="n">Get-Content</span><span class="w"> </span><span class="s1">'/etc/group'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$_</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s1">'^([^:]+):'</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nv">$group</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$Matches</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w">
                </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$group</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"</span><span class="nv">$wordToComplete</span><span class="s2">*"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$group</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">)</span><span class="w">
</span><span class="c"># or</span><span class="w">
</span><span class="n">New-ParamCompleter</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="nt">-Arguments</span><span class="w"> </span><span class="p">@{</span><span class="w">
    </span><span class="nx">Name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'color'</span><span class="p">;</span><span class="w">
    </span><span class="nx">Script</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">param</span><span class="p">([</span><span class="n">string</span><span class="p">]</span><span class="w"> </span><span class="nv">$wordToComplete</span><span class="p">)</span><span class="w">
        </span><span class="n">Get-Content</span><span class="w"> </span><span class="s1">'/etc/group'</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="bp">$_</span><span class="w"> </span><span class="o">-match</span><span class="w"> </span><span class="s1">'^([^:]+):'</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nv">$group</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$Matches</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w">
                </span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$group</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s2">"</span><span class="nv">$wordToComplete</span><span class="s2">*"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$group</span><span class="w"> </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="section-9">ローカライズ</h3>
<p><a href="https://github.com/teramako/Sabamiso.completions/tree/main/completions">https://github.com/teramako/Sabamiso.completions/tree/main/completions</a> を見ると分かると思うが、<code>-Description</code> に指定する説明文を変数化している。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$msg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">data</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">ConvertFrom-StringData</span><span class="w"> </span><span class="sh">@'
    key1 = text1
    key2 = text2
    # ...
'@</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="n">Import-LocalizedData</span><span class="w"> </span><span class="nt">-BindingVariable</span><span class="w"> </span><span class="nx">localizedMessages</span><span class="w"> </span><span class="nt">-ErrorAction</span><span class="w"> </span><span class="nx">SilentlyContinue</span><span class="p">;</span><span class="w">
</span><span class="kr">foreach</span><span class="w"> </span><span class="p">(</span><span class="nv">$key</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$localizedMessages</span><span class="o">.</span><span class="nf">Keys</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nv">$msg</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$localizedMessages</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>説明文のローカライズを可能とするためだ。</p>
<p><code>completions/ja</code> 等のロケール・ディレクトリを作って <code>コマンド名.psd1</code> を入れておくと読み込まれて、<code>$msg</code>ディクショナリの各値が上書きされる。</p>
<p>参考: <a href="https://github.com/teramako/Sabamiso.completions/blob/main/completions/ja/whoami.psd1">https://github.com/teramako/Sabamiso.completions/blob/main/completions/ja/whoami.psd1</a></p>
<h3 id="section-10">既存のコマンド補完を再利用する</h3>
<p><a href="https://github.com/teramako/Sabamiso.psm/blob/main/README.md#example-3-use-posh-gits-completion">https://github.com/teramako/Sabamiso.psm/blob/main/README.md#example-3-use-posh-gits-completion</a> に記載しているが、
補完用のモジュールが既にあってそれを再利用することも可能だ。
しかも、必要になった時に読み込まれる遅延読み込みのおまけつきで。</p>
<p>例えば、git コマンドの補完は <code>posh-git</code> があり、これを使用したい場合、以下のようなコードを <code>&lt;プロファイル・ディレクトリ&gt;/completions/git.ps1</code> に記述することで実現できる。
プロファイル内で <code>posh-git</code> を無駄に読み込む必要もなくなる。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">&lt;#
</span><span class="cs">.SYNOPSIS</span><span class="cm">
    Regsiter `git` command completer with `posh-git`
</span><span class="cs">.DESCRIPTION</span><span class="cm">
    This script will be loaded by `Sabamiso.psm` poershell module.
</span><span class="cs">.LINK</span><span class="cm">
    dahlbyk/posh-git: A PowerShell environment for Git
    https://github.com/dahlbyk/posh-git
#&gt;</span><span class="w">
</span><span class="kr">param</span><span class="p">(</span><span class="nv">$wordToComplete</span><span class="p">,</span><span class="w"> </span><span class="nv">$commandAst</span><span class="p">,</span><span class="w"> </span><span class="nv">$cursorPosition</span><span class="p">)</span><span class="w">
</span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">posh-git</span><span class="w">

</span><span class="c"># Reset the variable in the global scope</span><span class="w">
</span><span class="nv">$</span><span class="nn">global</span><span class="p">:</span><span class="nv">GitPromptScriptBlock</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$GitPromptScriptBlock</span><span class="w">

</span><span class="c"># The first time, generate the completion list manually</span><span class="w">
</span><span class="n">TabExpansion2</span><span class="w"> </span><span class="nt">-inputScript</span><span class="w"> </span><span class="nv">$commandAst</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="o">.</span><span class="nf">PadRight</span><span class="p">(</span><span class="nv">$cursorPosition</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">              </span><span class="nt">-cursorColumn</span><span class="w"> </span><span class="nv">$cursorPosition</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-ExpandProperty</span><span class="w"> </span><span class="nx">CompletionMatches</span><span class="w">
</span></code></pre></div></div>
<p><code>Import-Module posh-git</code> でモジュールを読み込む。すると、<code>posh-git</code> 側で <code>Register-ArgumentCompleter -CommandName git ...</code> の補完が登録される。
(次回以降は)<code>Register-ArgumentCompleter -CommandName git ...</code>で登録された補完スクリプトが呼ばれることになる。
初回は <code>TabExpansion2</code> 関数を手動で呼んで補完させる。</p>
<p>コマンド自身が補完候補を返すような、たとえば <code>dotnet complete</code> のようなものの場合、<code>&lt;プロファイル・ディレクトリ&gt;/completions/dotnet.ps1</code> に以下のようなコードを書く。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">&lt;#
</span><span class="cs">.SYNOPSIS</span><span class="cm">
    Regsiter `dotnet` command completer
</span><span class="cs">.DESCRIPTION</span><span class="cm">
    This script will be loaded by `Sabamiso.psm` poershell module.
</span><span class="cs">.LINK</span><span class="cm">
    How to enable tab completion for the .NET CLI
    https://learn.microsoft.com/en-us/dotnet/core/tools/enable-tab-autocomplete
#&gt;</span><span class="w">
</span><span class="kr">param</span><span class="p">(</span><span class="nv">$wordToComplete</span><span class="p">,</span><span class="w"> </span><span class="nv">$commandAst</span><span class="p">,</span><span class="w"> </span><span class="nv">$cursorPosition</span><span class="p">)</span><span class="w">

</span><span class="n">Register-ArgumentCompleter</span><span class="w"> </span><span class="nt">-Native</span><span class="w"> </span><span class="nt">-CommandName</span><span class="w"> </span><span class="nx">dotnet</span><span class="w"> </span><span class="nt">-ScriptBlock</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">(</span><span class="nv">$wordToComplete</span><span class="p">,</span><span class="w"> </span><span class="nv">$commandAst</span><span class="p">,</span><span class="w"> </span><span class="nv">$cursorPosition</span><span class="p">)</span><span class="w">
    </span><span class="n">dotnet</span><span class="w"> </span><span class="nx">complete</span><span class="w"> </span><span class="nt">--position</span><span class="w"> </span><span class="nv">$cursorPosition</span><span class="w"> </span><span class="nv">$commandAst</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ForEach-Object</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="p">[</span><span class="n">System.Management.Automation.CompletionResult</span><span class="p">]::</span><span class="n">new</span><span class="p">(</span><span class="bp">$_</span><span class="p">,</span><span class="w"> </span><span class="bp">$_</span><span class="p">,</span><span class="w"> </span><span class="s1">'ParameterValue'</span><span class="p">,</span><span class="w"> </span><span class="bp">$_</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="c"># The first time, generate the completion list manually</span><span class="w">
</span><span class="n">TabExpansion2</span><span class="w"> </span><span class="nt">-inputScript</span><span class="w"> </span><span class="nv">$commandAst</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="o">.</span><span class="nf">PadRight</span><span class="p">(</span><span class="nv">$cursorPosition</span><span class="p">)</span><span class="w"> </span><span class="se">`
</span><span class="w">              </span><span class="nt">-cursorColumn</span><span class="w"> </span><span class="nv">$cursorPosition</span><span class="w"> </span><span class="se">`
</span><span class="w">    </span><span class="o">|</span><span class="w"> </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-ExpandProperty</span><span class="w"> </span><span class="nx">CompletionMatches</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[去年(2025年)末に PowerShell で外部コマンドの補完 で PowerShell 7.6 から Register-ArgumentCompleter に -NativeFallback のパラメーターがされてコマンドライン補完のコードを包括的に行えるようになったことを書いた。]]></summary></entry><entry><title type="html">PowerShell で実行結果を自動的に変数に収める</title><link href="https://teramako.github.io/2026/08/07/Automatic-store-the-last-result.html" rel="alternate" type="text/html" title="PowerShell で実行結果を自動的に変数に収める" /><published>2026-08-07T22:10:00+09:00</published><updated>2026-08-07T22:10:00+09:00</updated><id>https://teramako.github.io/2026/08/07/Automatic-store-the-last-result</id><content type="html" xml:base="https://teramako.github.io/2026/08/07/Automatic-store-the-last-result.html"><![CDATA[<p>昔、どこかで見たTips。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$PSDefaultParameterValues</span><span class="p">[</span><span class="s2">"Out-Default:OutVariable"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"LAST"</span><span class="w">
</span></code></pre></div></div>
<p>上記コードを <code>$PROFILE</code> に仕込んでおくと、コマンド実行結果が <code>$LAST</code> 変数に入る。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">Get-ChildItem</span><span class="w">

    </span><span class="n">Directory:</span><span class="w"> </span><span class="o">....</span><span class="w">

</span><span class="n">Mode</span><span class="w">                 </span><span class="nx">LastWriteTime</span><span class="w">         </span><span class="nx">Length</span><span class="w"> </span><span class="nx">Name</span><span class="w">
</span><span class="o">----</span><span class="w">                 </span><span class="o">-------------</span><span class="w">         </span><span class="o">------</span><span class="w"> </span><span class="o">----</span><span class="w">
</span><span class="n">d----</span><span class="w">          </span><span class="nx">2022/05/24</span><span class="w">    </span><span class="nx">17:12</span><span class="w">                </span><span class="o">*********</span><span class="w">
</span><span class="o">...</span><span class="w">

</span><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nv">$LAST</span><span class="w">

    </span><span class="n">Directory:</span><span class="w"> </span><span class="o">....</span><span class="w">

</span><span class="n">Mode</span><span class="w">                 </span><span class="nx">LastWriteTime</span><span class="w">         </span><span class="nx">Length</span><span class="w"> </span><span class="nx">Name</span><span class="w">
</span><span class="o">----</span><span class="w">                 </span><span class="o">-------------</span><span class="w">         </span><span class="o">------</span><span class="w"> </span><span class="o">----</span><span class="w">
</span><span class="n">d----</span><span class="w">          </span><span class="nx">2022/05/24</span><span class="w">    </span><span class="nx">17:12</span><span class="w">                </span><span class="o">*********</span><span class="w">
</span><span class="o">...</span><span class="w">

</span><span class="n">PS1</span><span class="err">&gt;</span><span class="w">
</span></code></pre></div></div>
<p>直前のコマンド実行結果を再度確認したい時や、加工したい場合に便利。</p>
<h2 id="section">仕組み</h2>
<h3 id="out-default-">Out-Default コマンドレット</h3>
<ul>
<li><a href="https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/out-default?view=powershell-7.6" title="Out-Default (Microsoft.PowerShell.Core) - PowerShell | Microsoft Learn">Out-Default</a></li>
</ul>
<p>PowerShell はコマンドラインをAST解析して、各コマンドをまとめたパイプラインにまとめる。
このパイプラインの最後のコマンドがFormat系(<code>Format-List</code>や<code>Format-Table</code>)でない場合に、暗黙的に<code>Out-Default</code>を末尾に追加する動きをしている。</p>
<ul>
<li><code>Get-ChildItem</code> → <code>Get-ChildItem | Out-Default</code></li>
<li><code>Get-ChildItem | Select-Object ...</code> → <code>Get-ChildItem | Select-Object ... | Out-Default</code></li>
<li><code>Get-ChildItem | Format-List ...</code> → <code>Get-ChildItem | Format-List ...</code> (末尾が<code>Format-*</code> なので<code>Out-Default</code> は追加されない)</li>
</ul>
<h3 id="code-outvariablecode-"><code>-OutVariable</code> パラメーター</h3>
<ul>
<li><a href="https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_commonparameters?view=powershell-7.6#-outvariable" title="-OutVariable - 共通パラメーターについて - PowerShell | Microsoft Learn">about_CommonpPrameters</a></li>
</ul>
<p>また、すべてのコマンドレットには、出力結果を引数に指定した変数名の変数に収める <code>-OutVariable</code> というパラメーターを指定できる。
パイプラインに出力するオブジェクトを出力と同時に変数に収める。</p>
<p>変数は <code>ArrayList</code> 型で、出力が単一であっても配列になる点に注意。</p>
<h3 id="codepsdefaultparametervaluescode-"><code>$PSDefaultParameterValues</code> 変数</h3>
<ul>
<li><a href="https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_parameters_default_values?view=powershell-7.6" title="about_Parameters_Default_Values - PowerShell | Microsoft Learn">about_Parameters_Default_Values</a></li>
</ul>
<p><code>$PSDefaultParameterValues</code> のディクショナリのキーに <code>&lt;コマンド名&gt;:&lt;パラメーター名&gt;</code>、値にそのパラメーターの値を入れるとパラメーターの規定値を設定できる。</p>
<hr />
<p>これらを組み合わせる。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$PSDefaultParameterValues</span><span class="p">[</span><span class="s2">"Out-Default:OutVariable"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"LAST"</span><span class="w">
</span></code></pre></div></div>
<p>すると、<code>Get-ChildItem ...</code> は <code>Get-ChildItem | Out-Default -OutVariable LAST</code> に変換される。</p>
<h2 id="section-1">使い道</h2>
<h3 id="section-2">フィルタリングする</h3>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">Get-ChildItem</span><span class="w">
</span><span class="o">...</span><span class="w">
</span><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nv">$LAST</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Where-Object</span><span class="w"> </span><span class="nx">Length</span><span class="w"> </span><span class="o">-gt</span><span class="w"> </span><span class="nx">1MB</span><span class="w">
</span></code></pre></div></div>
<h3 id="section-3">直前の結果をログに残す</h3>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">Get-ChildItem</span><span class="w">
</span><span class="o">...</span><span class="w">
</span><span class="n">PS1</span><span class="err">&gt;</span><span class="w"> </span><span class="nv">$LAST</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">ConvertTo-Json</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Set-Content</span><span class="w"> </span><span class="nx">file-list.json</span><span class="w">
</span></code></pre></div></div>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[昔、どこかで見たTips。]]></summary></entry><entry><title type="html">Windows coreutils と PowerShell</title><link href="https://teramako.github.io/2026/08/07/Windows-coreutils-and-PowerShell.html" rel="alternate" type="text/html" title="Windows coreutils と PowerShell" /><published>2026-08-07T00:03:00+09:00</published><updated>2026-08-07T00:03:00+09:00</updated><id>https://teramako.github.io/2026/08/07/Windows-coreutils-and-PowerShell</id><content type="html" xml:base="https://teramako.github.io/2026/08/07/Windows-coreutils-and-PowerShell.html"><![CDATA[<h2 id="core-utils">core-utils</h2>
<p>Unix系のコマンドが使えるようになる <a href="https://learn.microsoft.com/ja-jp/windows/core-utils/overview" title="Windowsの Coreutils の概要 | Microsoft Learn">core-utils</a> がMicrosoftからリリースされている。
(参考: <a href="https://forest.watch.impress.co.jp/docs/news/2113918.html" title="「Coreutils for Windows」が一般提供 ～Linuxなどの定番コマンドをWindowsでネイティブ実行 - 窓の杜">ニュースにもなった</a>)</p>
<ul>
<li>リポジトリ: <a href="https://github.com/microsoft/coreutils">https://github.com/microsoft/coreutils</a></li>
</ul>
<p>普通にインストールすると、<code>C:\Program Files\coreutils</code> にインストールされる。
<code>C:\Program Files\coreutils\bin</code> に各exeファイルが入っている。
各exeファイルすべてが 8MB 近くあるように見えるが、ハードリンクで構成されているため実態は小さい。</p>
<p>ちょっと面白いのは <code>coreutils-manager.exe</code> があって、このコマンドで使うコマンドの管理(ハードリンクの作成(enable)/削除(disable))ができる。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">&gt;</span><span class="w"> </span>coreutils-manager disable <span class="nb">ls</span>
</code></pre></div></div>
<p>とすると <code>ls.exe</code> が消える。有効にするなら <code>coreutils-manager enable ls</code> とすれば良い。
<code>coreutils-manager status</code> で状態一覧も見られる。</p>
<p>まぁこれ自体は良い。
Microsoft自身がネイティブで動くコマンドを用意してくれて喜ばしいことだ。</p>
<h2 id="section">お行儀が悪い</h2>
<p>ただ、かなりお行儀が悪いことをしてくれる。</p>
<p>バージョン 2026.6.16 時点での話だが、インストールすると勝手にPowerShellプロファイルにコードを仕込むのだ。以下のコードが追加される。
<a href="https://github.com/microsoft/coreutils/blob/a350b9cf5c9c178b1089902cde89275688862e32/src/pwsh-install-template.ps1">https://github.com/microsoft/coreutils/blob/a350b9cf5c9c178b1089902cde89275688862e32/src/pwsh-install-template.ps1</a></p>
<p>コマンドライン実行時に呼ばれる関数 <code>PSConsoleHostReadLine</code> を上書きして、coreutilsで有効になっているコマンド名のものがあったら、coreutilsのフルパスに変換してしまうようなことをしている。</p>
<p>PowerShell の動きとしてはコマンド部分をエイリアス、関数、コマンドレットから検索し、見つからなかったら PATH環境変数から外部コマンドを検索するが、フルパスに置き換えられるため、これらの検索は行われずにダイレクトに core-utils のコマンドが実行されることになる。
<code>ls</code> や <code>cat</code> など、PowerShell 自体がエイリアスとして <code>Get-ChildItem</code> や <code>Get-Content</code> になっていてもガン無視される。</p>
<h3 id="section-1">問題点</h3>
<ul>
<li>何の注意喚起もない</li>
<li>訓練されたPowerShellユーザーは <code>ls</code> は <code>Get-ChildItem</code> のエイリアスになっていて、外部コマンドよりも優先度が高いことを知っている。 <code>Get-ChildItem</code> のつもりで <code>ls</code> を実行して混乱することになる。</li>
<li><code>ls</code> のパラメーターをタブ補完しようとすると、PowerShell本体は <code>ls</code> を <code>Get-ChildItem</code> と認識しているので core-utilsの <code>ls.exe</code> ではなく <code>Get-ChildItem</code> のパラメーターを補完するになり不整合が生じる</li>
</ul>
<h3 id="section-2">インストール時と言ったが、それは嘘だ</h3>
<p>正確には、冒頭で挙げた <code>coreutils-manager.exe</code> でコマンドの有効化/無効化を行ってもプロファイルの仕込みが走る。</p>
<h3 id="section-3">無効化する術が分からない</h3>
<p>インストール/アップデート時のみ気を付けておけば良いとは限らないことが分かったので、本格的に無効化する（書き換えが走っても実質スキップされるような）術を探った。</p>
<p>が、結論としては分からなかったorz</p>
<p>プロファイルには以下のようなヘッダー、フッターが付いている。
ここだけ残せば書き換えがコードの仕込みを抑制できるのでは？ → ダメだった。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># DO NOT MODIFY -- coreutils -- 60b36fc6-2d59-49df-be51-28dd2f4c3c9a</span><span class="w">
</span><span class="c"># vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv</span><span class="w">
</span><span class="o">...</span><span class="w">
</span><span class="c"># ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</span><span class="w">
</span><span class="c"># DO NOT MODIFY -- coreutils -- 60b36fc6-2d59-49df-be51-28dd2f4c3c9a</span><span class="w">
</span></code></pre></div></div>
<p>途中の <code>function PSConsoleHostReadLine { ... }</code> を実質何もしないコードにしておく案は？ → ダメだった。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">function</span><span class="w"> </span><span class="nf">PSConsoleHostReadLine</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="p">()</span><span class="w">
    </span><span class="kr">return</span><span class="w"> </span><span class="p">[</span><span class="n">Microsoft.PowerShell.PSConsoleReadLine</span><span class="p">]::</span><span class="n">ReadLine</span><span class="p">(</span><span class="nv">$host</span><span class="o">.</span><span class="nf">Runspace</span><span class="p">,</span><span class="w"> </span><span class="bp">$ExecutionContext</span><span class="p">,</span><span class="w"> </span><span class="bp">$?</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="issue-">Issue になっている</h3>
<ul>
<li><a href="https://github.com/microsoft/coreutils/issues/161">https://github.com/microsoft/coreutils/issues/161</a></li>
</ul>
<p>まぁ当然のようにIssueに登録されている。</p>
<p>修正されるのを待つしかない。</p>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[core-utils]]></summary></entry><entry><title type="html">PowerShell で外部コマンドの補完</title><link href="https://teramako.github.io/2025/12/16/PowerShell-NativeCommandCompletion.html" rel="alternate" type="text/html" title="PowerShell で外部コマンドの補完" /><published>2025-12-16T10:57:00+09:00</published><updated>2025-12-16T10:57:00+09:00</updated><id>https://teramako.github.io/2025/12/16/PowerShell-NativeCommandCompletion</id><content type="html" xml:base="https://teramako.github.io/2025/12/16/PowerShell-NativeCommandCompletion.html"><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>PowerShell で外部コマンドの補完をするモジュールを開発中だよ</p>
<ul>
<li><a href="https://github.com/teramako/NativeCommandCompleter.psm">teramako/NativeCommandCompleter.psm</a></li>
</ul>
<hr />
<p>なんだかんだ不満がありつつも、Linux(WSL)上で PowerShell を触っている今日この頃。</p>
<p>ただの文字列ではなくオブジェクトとして扱えることや、.NET の資産を使えることが大きい。
ただ、PowerShell 内のみの Cmdlet だけでは窮屈で外部コマンドを使用したいことが多々ある。というか外部コマンドでないとできないことが多い。
そうなるとPowerShellは無力だ。</p>
<p>出力はただの文字列だし、エラーハンドリングは面倒だし、コマンドラインの補完が全く効かないし。</p>
<p>そう、コマンドライン補完だ。</p>
<p>Linux の bash, zsh, fish 等々のシェルは外部コマンドの補完ができる。
この補完のおかげでコマンドライン生活ができるのだ。</p>
<p>PowerShell だってその点は分かっている。
<a href="https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/register-argumentcompleter">Register-ArgumentCompleter</a> を用いて外部コマンドの補完スクリプトの登録が可能だ。</p>
<p>でも、これ、<code>$PROFILE</code> 等に一つ一つ丁寧に各コマンドの補完スクリプトを登録する必要がある。</p>
<p>Linux上で外部コマンドが幾つあると思っているんだ。</p>
<h2 id="powershell-760-preview5">PowerShell 7.6.0-preview.5</h2>
<p>そんな不満を持っていたところ、<a href="https://github.com/PowerShell/PowerShell/releases/tag/v7.6.0-preview.5">PowerShell 7.6.0-preview.5</a> がリリースされた。</p>
<blockquote>
<p>Add the parameter <code>Register-ArgumentCompleter -NativeFallback</code> to support registering a cover-all completer for native commands (<a href="https://github.com/PowerShell/PowerShell/pull/25230">#25230</a>)</p>
</blockquote>
<p>なにやら面白そうなのが追加されているじゃないですか。</p>
<h2 id="register-argumentcompleter--nativefallback">Register-ArgumentCompleter -NativeFallback</h2>
<p>簡単に言うと、全コマンドで補完時に <code>Register-ArgumentCompleter</code> に与えたコードを通すことができるようになった。</p>
<p>これまでは、
<code>Register-ArgumentCompleter -CommandName &lt;string[]&gt; -ScriptBlock &lt;scriptblock&gt; [-Native] [&lt;CommonParameters&gt;]</code> で、外部コマンドの補完コードを定義していたが、<code>-CommandName</code> コマンド名の指定が必要だった。</p>
<p>これが、<code>Register-ArgumentCompleter -ScriptBlock &lt;scriptblock&gt; [-NativeFallback] [&lt;CommonParameters&gt;]</code> と、コマンド指定不要でコード登録ができるようになった。</p>
<ol>
<li>補完開始</li>
<li>コマンドライン解析</li>
<li>コマンド指定がある
<ul>
<li>PowerShell コマンド(Cmdlet) → Cmdletの補完(PowerShell内部コード)</li>
<li>コマンド名指定で補完コードが登録されている → その補完(登録されたスクリプト実行)</li>
<li><strong>NEW</strong>: 上記2つに該当しない → 登録されたスクリプト実行(フォールバック的に)</li>
<li>上記を通して補完候補がない → ファイル・ディレクトリ名補完</li>
</ul>
</li>
</ol>
<p>といった感じ。</p>
<h3 id="section">補完コードの動的読み込み</h3>
<p>Linux のシェル ―― bash, zsh, fish 等では、 <code>/etc/bash_completion.d</code> 等に各コマンドの補完コードが入っていて、必要に応じて読み込まれる。</p>
<p>これらと同じ事ができるよね？</p>
<p>早速試したが、…できた。</p>
<h2 id="section-1">モジュール開発</h2>
<p>ということで、開発中である。</p>
<ul>
<li><a href="https://github.com/teramako/NativeCommandCompleter.psm">teramako/NativeCommandCompleter.psm</a></li>
</ul>
<p>fish の補完が気に入っているので、強くインスパイアされた補完リスト出力になっている。</p>
<p>組み込みで補完可能なコマンドの定義は以下にある</p>
<ul>
<li><a href="https://github.com/teramako/NativeCommandCompleter.psm/tree/main/completions">https://github.com/teramako/NativeCommandCompleter.psm/tree/main/completions</a></li>
</ul>
<p>メインはLinuxコマンドの補完だが、Windowsのコマンドも補完できるようにクロスプラットフォームに気を使っている（つもり）。
おかげでWindows用やLinux用(一部macOS/BSD用もあり)がごちゃまぜ状態…。</p>
<p>モジュールと補完定義は別リポジトリにすべきだったと少し後悔している。
C# プロジェクトだったのに、ご覧のありさまだ。</p>
<p><img src="/img/2025-12-16/github-languages.png" alt="languages" /></p>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[TL;DR PowerShell で外部コマンドの補完をするモジュールを開発中だよ]]></summary></entry><entry><title type="html">PowerShell のロケール設定</title><link href="https://teramako.github.io/2025/10/14/Set-Locale-for-PowerShell.html" rel="alternate" type="text/html" title="PowerShell のロケール設定" /><published>2025-10-14T22:30:00+09:00</published><updated>2025-10-14T22:30:00+09:00</updated><id>https://teramako.github.io/2025/10/14/Set-Locale-for-PowerShell</id><content type="html" xml:base="https://teramako.github.io/2025/10/14/Set-Locale-for-PowerShell.html"><![CDATA[<p>Windows 標準で入っている PowerShell 5.1 ではなく、PowerShell 7 の話。</p>
<p>PowerShell モジュールを開発していてヘルプを英語で出したり日本語に切り替えたりしたくなった。</p>
<p>ヘルプの言語環境は <code>UICulture</code> が関わってくる。この値を設定し直せれば切り替え可能。</p>
<h2 id="section">結論</h2>
<ul>
<li><code>[System.Threading.Thread]::CurrentThread.CurrentUICulture</code> を設定せよ。</li>
</ul>
<h2 id="lang-">LANG 環境変数</h2>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LANG</span><span class="o">=</span>ja_JP.UTF-8 pwsh
</code></pre></div></div>
<p>LANG=環境変数は有効に働くみたい。
ただし、pwsh プロセスの再起動が必要。</p>
<h2 id="codepsuiculturecode"><code>$PSUICulture</code></h2>
<p>PowerShell の自動変数に、<code>$PSCulture</code> と <code>$PSUICulture</code> がある。
しかし、この変数は読取専用であり、ここに新たな値を設定することができない。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">PS&gt;</span><span class="w"> </span><span class="nv">$PSCulture</span> <span class="o">=</span> <span class="s2">"ja-JP"</span>
<span class="go">WriteError: Cannot overwrite variable PSCulture because it is read-only or constant.
</span></code></pre></div></div>
<h2 id="codecultureinfocurrentuiculturecode"><code>[cultureinfo]::CurrentUICulture</code></h2>
<p>こちらは設定可能で、有効に働いた。</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">cultureinfo</span><span class="p">]::</span><span class="n">CurrentUICulture</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"ja-JP"</span><span class="w">

</span><span class="n">Import-Module</span><span class="w"> </span><span class="nx">MyModule</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="n">Get-Help</span><span class="w"> </span><span class="nx">Test-Cmd</span><span class="w">
</span></code></pre></div></div>
<p>既に読み込んでいるモジュールは再読み込みさせないと切り替わらない。</p>
<p>あと、<code>$PSUICulture</code> に反映されない点がなんだか気持ち悪い。</p>
<h2 id="codesystemthreadingthreadcurrentthreadcurrentuiculturecode"><code>[System.Threading.Thread]::CurrentThread.CurrentUICulture</code></h2>
<p>現行スレッドのオブジェクトにも UICulture を設定できるようだったのでやってみたら、当りだった。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">PS&gt;</span><span class="w"> </span><span class="o">[</span>System.Threading.Thread]::CurrentThread.CurrentUICulture <span class="o">=</span> <span class="s2">"ja-JP"</span>
<span class="gp">PS&gt;</span><span class="w"> </span><span class="nv">$PSUICulture</span>
<span class="go">ja-JP

</span><span class="gp">PS&gt;</span><span class="w"> </span>Import-Module MyModule <span class="nt">-Force</span>
<span class="gp">PS&gt;</span><span class="w"> </span>Get-Help Test-Cmd
</code></pre></div></div>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><category term="PowerShell" /><summary type="html"><![CDATA[Windows 標準で入っている PowerShell 5.1 ではなく、PowerShell 7 の話。 [cultureinfo]::CurrentUICulture = &quot;ja-JP&quot;]]></summary></entry><entry><title type="html">TLS (SSL) 証明書生成 Makefile</title><link href="https://teramako.github.io/2025/06/26/certificates-generator.html" rel="alternate" type="text/html" title="TLS (SSL) 証明書生成 Makefile" /><published>2025-06-26T22:00:00+09:00</published><updated>2025-06-26T22:00:00+09:00</updated><id>https://teramako.github.io/2025/06/26/certificates-generator</id><content type="html" xml:base="https://teramako.github.io/2025/06/26/certificates-generator.html"><![CDATA[<ul>
<li><a href="https://gist.github.com/teramako/9352b4ab5860204b7d9f66119b33060b">https://gist.github.com/teramako/9352b4ab5860204b7d9f66119b33060b</a></li>
</ul>
<p>TLS(SSL) 証明書を作成する Makefile を作った。</p>
<ul>
<li>CA, サーバー証明書、クライアント証明書の生成が可能</li>
<li>ECDSA な秘密鍵生成にも対応</li>
<li>PKCS12 フォーマットへ出力が可能</li>
<li>x509 v3 拡張を使用
<ul>
<li>サーバー証明書には SAN 値の設定が可能</li>
</ul>
</li>
</ul>
<script src="https://gist.github.com/teramako/9352b4ab5860204b7d9f66119b33060b.js"></script>]]></content><author><name>teramako</name><email>terama@gmail.com</email></author><summary type="html"><![CDATA[https://gist.github.com/teramako/9352b4ab5860204b7d9f66119b33060b]]></summary></entry></feed>