しばたテックブログ

気分で書いている技術ブログです。

PowerShell既定のフォーマット設定に介入する方法について

きっかけはカナダのMVP、Thomas Raynerさんのブログエントリから。

thomasrayner.ca

PowerShellにおいて予め*.format.ps1xmlによって表示書式が定められてないオブジェクトは、表示されるプロパティの数が4個以下の場合はTableフォーマットを、5個以上になる場合はListフォーマットを取ります。*1

例としてプロパティの数が異なる簡単なPSCustomObjectを挙げます。

# PS 3.0以降の環境で再現可能
[PSCustomObject]@{Prop1=1;Prop2=2}
[PSCustomObject]@{Prop1=1;Prop2=2;Prop3=3}
[PSCustomObject]@{Prop1=1;Prop2=2;Prop3=3;Prop4=4}
[PSCustomObject]@{Prop1=1;Prop2=2;Prop3=3;Prop4=4;Prop5=5}

このコードを実行すると下図の様にプロパティの数が5になった時点でListフォーマットに変化していることが分かります。

f:id:stknohg:20171006030945p:plain

このプロパティ数の根拠は何なのか?

このはなしに関しては、どのドキュメントだったかは失念したのですが、以前にも聞いたことがありました。

当時は「そんなものなのかなぁ...」となんとなく流していたのですが今はPowerShellはオープンソースとなっています。
せっかくなのでソースからこのプロパティ数の根拠を調べてみました。

調査したソースのバージョンは現時点の最新であるPowerShell 6.0 Beta7ですが、PowerShellの根幹にかかわる部分ですので過去のバージョンでもさほど変わらないと思います。

調査結果

結果としては、Microsoft.PowerShell.Commands.Internal.Format.FormatViewManagerクラスのこのあたりが怪しそうです。

それっぽいコメントが記載されています。

// Microsoft.PowerShell.Commands.Internal.Format.FormatViewManagerクラスより抜粋

// we did not get any default view (and shape), we need to force one
// we just select properties out of the object itself, since they were not
// specified on the command line
_viewGenerator = SelectViewGeneratorFromProperties(shape, so, errorContext, expressionFactory, db, null);

で、このSelectViewGeneratorFromPropertiesメソッドは内部で、Microsoft.PowerShell.Commands.Internal.Format.DisplayDataQueryクラスのGetShapeFromPropertyCountメソッドを呼んでいます。
名前からして正解に近そうです。

// Microsoft.PowerShell.Commands.Internal.Format.FormatViewManagerクラスより抜粋
private static ViewGenerator SelectViewGeneratorFromProperties(FormatShape shape, PSObject so,
                            TerminatingErrorContext errorContext,
                            MshExpressionFactory expressionFactory,
                            TypeInfoDataBase db,
                            FormattingCommandLineParameters parameters)
{
    // いろいろ省略

    // decide what shape we want for the given number of properties
    shape = DisplayDataQuery.GetShapeFromPropertyCount(db, expressionList.Count);

    // 後略

このGetShapeFromPropertyCountメソッドの定義は以下の様になり、

// Microsoft.PowerShell.Commands.Internal.Format.DisplayDataQuery クラスより抜粋
internal static FormatShape GetShapeFromPropertyCount(TypeInfoDataBase db, int propertyCount)
{
    if (propertyCount <= db.defaultSettingsSection.shapeSelectionDirectives.PropertyCountForTable)
        return FormatShape.Table;

    return FormatShape.List;
}

表示対象となるプロパティの数が

db.defaultSettingsSection.shapeSelectionDirectives.PropertyCountForTable

以下であればTableフォーマット(FormatShape.Table)、そうでなければListフォーマット(FormatShape.List)を選ぶ様になっています。

いい感じです。
PropertyCountForTableの内容を調べれば正解にたどり着けそうです。

これはMicrosoft.PowerShell.Commands.Internal.Format.ShapeSelectionDirectivesクラスに定義されており、以下の様になっています。

// Microsoft.PowerShell.Commands.Internal.Format.ShapeSelectionDirectives クラスより抜粋
internal int PropertyCountForTable
{
    set
    {
        if (!_propertyCountForTable.HasValue)
        {
            _propertyCountForTable = value;
        }
    }
    get
    {
        if (_propertyCountForTable.HasValue)
            return _propertyCountForTable.Value;
        return 4;
    }
}
private int? _propertyCountForTable;

_propertyCountForTableに値が定義されてればその値を使い、値が定義されていない場合は4を返す様になっています。

詳細は後述しますが通常_propertyCountForTableには値がセットされておらず4を使う様になっています。
プロパティ数の4の根拠はここの様です。

PowerShell既定のフォーマット設定に介入する方法

ここから本エントリの本題に入ります。

先ほど判明したPropertyCountForTableプロパティおよび_propertyCountForTableについてですが、さらに調査をしたところ、PowerShellのオブジェクトの書式定義を行う.format.ps1xmlファイルから設定可能であることが分かりました。

既定の.format.ps1xmlファイルはPSHOMEディレクトリに複数用意されており、また、PowerShell 5.1からはこのファイルと対になる内部クラスを使う様に置き換えられていますが、これらのファイル・クラスを調べてもPropertyCountForTableプロパティを設定している個所はありませんでした。
このため先に述べた様に既定値の4が使われています。

.format.ps1xmlファイルはユーザーが独自に定義することが可能です。
独自に作成した.format.ps1xmlファイルからPropertyCountForTableプロパティに介入できないか試してみたところ、このプロパティの値を変えることができました。

手順は次の通りです。

最初に、以下の様なXMLファイルを作成して任意の名前で保存します。
PropertyCountForTableタグの値がPropertyCountForTableプロパティに指定する値となります。(今回は2にしています)

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
    <DefaultSettings>
        <PropertyCountForTable>2</PropertyCountForTable>
    </DefaultSettings>
</Configuration>

このXMLファイルをUpdate-FormatDataコマンドレットで読み込んでやればPropertyCountForTableプロパティの値を更新できます。

# XMLファイルの名称を sample.format.ps1xml とした場合
# -PretendPathでも大丈夫ぽい
Update-FormatData -AppendPath .\sample.format.ps1xml

実行例は以下の様になりフォーマットが変化するプロパティの数が2に変わっていることがわかります。

f:id:stknohg:20171006031006p:plain

ちなみに、本エントリはPowerShell 5.1の環境で動作確認していますが、PowerShell 2.0の環境でも介入可能でした。

最後に

とりあえずこんな感じです。
PowerShellがオープンソース化してくれたおかげで以前はわからなかった挙動に対する理解を深めることができました。

すばらしいですね。


【2017/10/06追記】

補足を書きました。

blog.shibata.tech

*1:標準で表示書式が定められているオブジェクトは設定された書式が優先されこのルールに従いません