先日、「PowerShell Crescendo Preview.1」が公開された。PowerShell Crescendo Preview.1がどのようなもので、どういったことを目的としたものであるかはMicrosoftのアナウンスにわかりやすくまとまっている。

Announcing PowerShell Crescendo Preview.1 | PowerShell


本連載では、WSLで動作するLinuxのコマンドをPowerShellからより簡単に利用できるようにする取り組みを続けてきた。PowerShellからLinuxのコマンドを使用できると何かと便利だからだ。

「PowerShell Crescendo」はネイティブコマンドをコマンドレットに置き換える機能を提供する。これまで本連載で取り組んできた設定とは、逆の取り組みのような感じだ。PowerShellとしては、ネイティブコマンドをコマンドレットにラッピングするほうが自然であり、PowerShell Crescendoはそれを実現するものとされている。

ネイティブコマンドをコマンドレットへ

PowerShellは「管理用のインタラクティブシェル」という側面を備えており、UNIXのインタラクティブシェルやコマンドプロンプトのようなUI/UXを備えている。しかし、実際にはC#などで実現されているオブジェクト指向やその機能に強く影響を受けたプログラミング言語であるとともに、命名規則やオプションの指定方法などにきっちりとした決まりがある。

UNIXシェルの命名規則などは慣例的なもので強制力はない。そこで動作するコマンドも個別に開発されたもので、コマンド名やオプションの指定、サブコマンドの指定やその命名規則などには一貫性がない。ある程度似通ったものではあるのだが、PowerShellのようにきっちりとは決まっていないのだ。

PowerShellでは、当然ネイティブコマンドも実行できるようになっている。しかし、ネイティブコマンドとPowerShellコマンドレットにはシンタックスに違いがあり、PowerShellに慣れたユーザーからしてみればネイティブコマンドをコマンドレットのように扱えたほうが便利だ。ネイティブコマンドごとに操作方法などを覚えるのは、PowerShellをメインに使っているユーザーにとっては負担となる。

先ほどの記事ではコマンドレットの特徴として次の項目を挙げている。

  • コマンドレット名の命名規則: 動詞-名詞
  • 統一されたパラメータ名
  • 入出力がオブジェクト
  • パイプライン内のオブジェクトを簡単に操作
  • コマンドヘルプを取得する一貫した方法


要するに、ネイティブコマンドをコマンドレットでラッピングして上記の特徴を備えてしまえば、PowerShellの流儀で同じように扱えるようになる、というのがPowerShell Crescendoの発想だ。

ネイティブコマンドには上記のような決まりはないし、規則もない。Linuxの管理者であればapt、docker、kubectlといった管理コマンドをよく使うと思うが、これらのコマンドはサブコマンドを指定して利用する。サブコマンド名の規則性はないし、コマンドごとに異なっている。Windowsであればnetsh.extコマンドもよく使うと思うが、これらも独自の指定方法だ。こういったネイティブコマンドの細かい違いを、コマンドレットでラッピングして使ってやろうというわけである。

PowerShell Crescendoのインストール方法

PowerShell CrescendoはPowerShell Galleryで提供されている。本稿執筆時点での最新版は「PowerShell.Crescendo 0.4.1」だ。


マニュアルインストールしてもよいが、以下のようにInstall-Moduleコマンドレットでインストールして使うのがよいだろう。

Install-Module -Name Microsoft.PowerShell.Crescendo

マニュアルは次のようにコマンドレットを実行することで表示させることができる。

Get-Help about_Crescendo

PowerShell Crescendoの使用サンプル

PowerShell Crescendoの使い方は、ネイティブコマンドをどのようにコマンドレットに変換するかを定義してJSONファイルを作成する、という流れになる。JSONファイルの書き方は冒頭で紹介したアナウンスに掲載されているサンプルがわかりやすい。例えば、次のJSONファイルはLinuxのパッケージ管理コマンドであるaptコマンドを「Get-InstalledPackage」というコマンドレットに変換(ラッピング)する設定だ。

{
    "$schema": "./Microsoft.PowerShell.Crescendo.Schema.json", // <-- Schema definition file
    "Verb": "Get", // <-- Cmdlet Verb
    "Noun":"InstalledPackage", // <-- Cmdlet Noun
    "OriginalName": "apt", // <-- Native command
    "OriginalCommandElements": ["-q","list","--installed"],
    "OutputHandlers": [ // <-- Add string output to an object
        {
            "ParameterSetName":"Default",
            "Handler": "$args[0]| select-object -skip 1 | %{$n,$v,$p,$s = "$_" -split ' '; [pscustomobject]@{ Name = $n -replace '/now'; Version = $v; Architecture = $p; State = $s.Trim('[]') -split ',' } }"
        }
    ]
}

次がラッピングしたGet-InstalledPackageコマンドレットの実行サンプルだ。aptコマンドが何となくコマンドレットっぽく使えていることがわかる。

PS> Get-InstalledPackage | Where-Object { $_.name -match "libc"}

Name        Version            Architecture State
----        -------            ------------ -----
libc-bin    2.31-0ubuntu9.1    amd64        {installed, local}
libc6       2.31-0ubuntu9.1    amd64        {installed, local}
libcap-ng0  0.7.9-2.1build1    amd64        {installed, local}
libcom-err2 1.45.5-2ubuntu1    amd64        {installed, local}
libcrypt1   1:4.4.10-10ubuntu4 amd64        {installed, local}

PS> Get-InstalledPackage | Group-Object -Property Architecture

Count Name   Group
----- ----   -----
   10 all    {@{Name=adduser; Version=3.118ubuntu2; Architecture=all; State=System.String[}, @{Name=debconf; V...
   82 amd64  {@{Name=apt; Version=2.0.2ubuntu0.1; Architecture=amd64; State=System.String[]}, @{Name=base-files...

次のJSONにファイルはWindowsのipconfig.exeコマンドをGet-IpConfigコマンドレットへラッピングする設定が掲載されている。パラメータを定義しており、さきほどのJSONファイルのサンプルよりも複雑になっている。

{
    "$schema" : "./Microsoft.PowerShell.Crescendo.Schema.json",
    "Verb": "Get",
    "Noun": "IpConfig",
    "OriginalName":"c:/windows/system32/ipconfig.exe",
    "Description": "This will display the current IP configuration information on Windows",

    "Parameters": [ // <-- Parameter definition
        {
            "Name":"All", // <-- Name of parameter that appears in cmdlet
            "OriginalName": "/all", // <-- Name of original parameter that appears in native cmd
            "ParameterType": "switch",
            "Description": "This switch provides all ip configuration details" // <-- Help parameter
        },
        {
            "Name":"AllCompartments",
            "OriginalName": "/allcompartments",
            "ParameterType": "switch",
            "Description": "This switch provides compartment configuration details"
        }
    ],

    "OutputHandlers": [
        {
        "ParameterSetName": "Default",
        "Handler":"param ( $lines )
        $post = $false;
        foreach($line in $lines | ?{$_.trim()}) {
            $LineToCheck = $line | select-string '^[a-z]';
            if ( $LineToCheck ) {
                if ( $post ) { [pscustomobject]$ht |add-member -pass -typename $oName }
                $oName = ($LineToCheck -match 'Configuration') { 'IpConfiguration' } else {'EthernetAdapter'}
                $ht = @{};
                $post = $true
            }
            else {
                if ( $line -match '^   [a-z]' ) {
                    $prop,$value = $line -split ' :',2;
                    $pName = $prop -replace '[ .-]';
                    $ht[$pName] = $value.Trim()
                }
                else {
                    $ht[$pName] = .{$ht[$pName];$line.trim()}
                }
            }
        }
        [pscustomobject]$ht | add-member -pass -typename $oName"
        }
    ]
}

実行すると次のようになる。ipconfig.exeコマンドがあたかも最初からコマンドレットだったかのように動作していることがわかる。

PS> Import-Module .ipconfig.psm1
PS> Get-IpConfig -All | Select-Object -Property ipv4address, DefaultGateway, subnetmask

ipv4address  DefaultGateway                              subnetmask
-----------  --------------                              ----------

172.24.112.1                                             255.255.240.0
192.168.94.2 {fe80::145e:1779:5cfa:c2a5%8, 192.168.94.1} 255.255.255.0

コマンドレットにラッピングしてしまうと、まるで最初からそのようなコマンドレットが存在しているかのように見える。なかなか不思議な感覚だ。

プレビュー版が登場したばかりなので何とも言えないのだが、多くのユーザーはPowerShell Crescendoを直接使うことはないのではないかと思う。ロジック的には自然な発想だと思うので、人気が高まっていけば、PowerShell Crescendoを使って開発されたコマンドレットを知らず知らずのうちに使っている、といった状況になっていくのではないだろうか。