しばたテックブログ

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

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

前回のエントリに対する補足を軽く書いておきます。

blog.shibata.tech

format.ps1xmlのDefaultSettingsタグ

PowerShell既定のフォーマット設定に介入するには、適当なforamt.ps1xmlファイルを作ってPropertyCountForTableタグに更新したい値を設定すればよいと説明しました。

設定例)

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

このタグの親であるDefaultSettingsですが、これはPowrerShellのフォーマット設定の既定値に関するタグとなりPropertyCountForTable以外にも複数のタグがあることが確認できました。

ただし、どのタグも通常は一切使われていないため何のためのものか・設定するとどういった効果があるかは正直謎です...

謎なのですが、せっかくなので見つけたタグについてわかる範囲で補足しておきます。

ShowErrorタグ および DisplayErrorタグ

どちらのタグもBool型の値を取り、既定値はFalseでした。

ShowErrorタグの説明*1には、

if true, display error messages

DisplayErrorタグの説明には

if true, display an error string in the formatted display (e.g. cell in a table)

と記載されておりフォーマットの内部処理でエラーが発生した際にエラー情報を出すか否かを制御するのに使われる様です。
恐らくデバッグ用の項目でしょう。

EnumerableExpansions -> EnumerableExpansion -> Expandタグ

階層が深いのですがExpandタグ以外のタグは無い様です。
このタグはMicrosoft.PowerShell.Commands.Internal.Format.EnumerableExpansion列挙型となり以下の様に定義されています。

internal enum EnumerableExpansion
{
    /// <summary>
    /// process core only, ignore IEumerable
    /// </summary>
    CoreOnly,

    /// <summary>
    /// process IEnumerable, ignore core
    /// </summary>
    EnumOnly,

    /// <summary>
    /// process both core and IEnumerable, core first
    /// </summary>
    Both,
}

既定値はEnumOnlyです。
名前からして配列などのオブジェクトの展開方法に関わっている様ですが、具体的に何をしているのかさっぱりわかりませんでした...

今後わかることがあれば追記します。

設定例

最後にこれらのタグの設定例を記載しておきます。

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
    <DefaultSettings>
        <ShowError>False</ShowError>
        <DisplayError>False</DisplayError>
        <EnumerableExpansions>
            <EnumerableExpansion>
                <Expand>EnumOnly</Expand>
            </EnumerableExpansion>
        </EnumerableExpansions>
    </DefaultSettings>
</Configuration>

*1:正確には対となるオブジェクトのプロパティのコメント

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:標準で表示書式が定められているオブジェクトは設定された書式が優先されこのルールに従いません

Redmine 3.4.2でrake redmine:migrate_from_tracをエラー無く動作させるためのパッチを書きました

タイトル通りです。

Trac LightningからRedmineへ移行するにあたり、公式?に用意されていた移行ツールがRedmine 3.4.2ではエラーが出て動かなかったので、とりあえずエラー無く動作させるパッチを書いてみました。

Redmine 3.4.2でrake redmine:migrate_from_tracをエラー無く動作させるためのパッチ

パッチはGistに上げています。

Redmine 3.4.2でrake redmine:migrate_from_tracをエラー無く動作させるためのパッチ

gist.github.com

パッチの詳細や使い方についてはコメントに書いてあります。

Tracからのデータ移行に関して

RedmineでTracからデータ移行をするにはrake redmine:migrate_from_tracコマンドを使います。
だだ、このコマンドはTrac 0.11あたりを対象としており、作られた時期もかなり昔の様です。
残念ながら現在はコードのメンテナンスが行われておらず、何も考えず実行すると以下の様にエラーになってしまいます。

f:id:stknohg:20170930120311p:plain

出ているエラーはRedmine内部で使用しているRailsのバージョンが上がったことが原因であるため、本エントリのパッチは単純にエラーとなっている個所を新しいRailsに合わせた形に直しているだけとなります。

パッチを当てた後で再度rake redmine:migrate_from_tracを実行すると下図の様にウィザードを進めることができ、エラー無くデータを移行することができます。

f:id:stknohg:20170930120350p:plain

f:id:stknohg:20170930120411p:plain

注意事項

Ver.1.0以降の新しいTracだとデータの内部形式が異なる様で、本パッチを適用しても「データベースが見つからない」旨のエラーが出てしまいます。
新しいバージョンのTracからデータを移行したい場合は気合いでなんとかするしかない様です。

私の場合、Trac Lightningは新しいバージョンでもTrac 0.12だったので運よくパッチだけで済みました...

このツールに関して、エラーに関するIssueも幾つか出ているのですが、中の人たちの優先度も低い様で改善される見込みは無いと思われます。
(仮に私が中の人だとしても間違いなく優先度は低くするでしょうし、むしろこのツールをサクッと捨てると思います...)

また、Gistにも記載していますが、Trac 0.12のデータを移行する際は、

migrate_from_trac does not support trac 0.12

のパッチも適用しておく必要があります。
これはTrac 0.12から時刻データの内部保持形式が変わった(UnixTimeからマイクロ秒単位のUnixTimeに変更されている)のに対応するパッチとなっています。