PowerShellでTodo管理 その1

なんとなくTodoアプリを作りたくなったので、PoweShellで作ってみる。

最初はスクリプトで作ろうかと思ったけど、無理がありそうだったのでカスタムProviderでやることにした。

PowerShellの特徴として、種類の違うデータに対して同一のコマンドで操作を行えるというのがある。FileSystemに対してもRegistryに対してもNew-Itemコマンドレットでアイテムの追加ができるというあれ。

この話自体は知っていたけど、実際どんなメカニズムなのか知らないので、それの勉強も兼ねてという意味あいもある。

作りたい物のイメージとしては、

PS Todo:\> dir

出力

完了    タイトル    詳細
----    ----        ----
false   テスト      テストです
true    テスト2    テストでした

Todoというドライブで「dir」をするとTodoアイテムの一覧が表示される。もちろんアイテムの追加や削除なんかも他のプロバイダと同様に、New-Item, Remove-Itemコマンドレットでできる。

実用性は低いけどPowerShellの可能性を垣間見る事はできるかもしれん。

準備

開発環境にはVisual Studio 2005 Professional Edtionを使用する。

PowerShellで独自のCmdletやProviderを作るにはPowerShellをインストールするとGACに登録されるSystem.Management.Automation.dll が必要になる。

GACにしかインストールされていないので、まずはこれを「C:\WINDOWS\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35」フォルダから適当な場所にコピーしてくる。

プロジェクトの作成

「クラスライブラリ」プロジェクトで新規プロジェクトを作成して、先程コピーしたSystem.Management.Automation.dllを参照設定に追加しておく。プロジェクト名は「PSTodo」にしておく。

で、カスタムのProviderを作るわけだが全然情報が無いので、オブジェクトブラウザとにらめっこすること数分。

まず、Provider関係のクラスはSystem.Management.Automation.Provider というそのまんまの名前空間の下にある。

FileSystemProviderとかRegistryProviderというクラスがMicrosoft.PowerShell.Commands という名前空間にあったので、クラス階層を探ってみた。

Providerクラス階層
  • CmdletProvider
    • DriveCmdletProvider
      • ItemCmdletProvider
        • ContainerCmdletProvider
          • NavigationCmdletProvider
            • FileSystemProvider
            • RegistryProvider
            • SessionStateProviderBase
              • AliasProvider
              • EnvironmentProvider
              • FunctionProvider
              • VariableProvider

NavigationCmdletProvider を基本クラスとして使っているのがわかる。そこから派生してSessionStateProviderBase という抽象クラスがあるが、ここから派生しているクラスを見ると、この抽象クラスがセッション毎に情報を持つProviderを実装する時に継承するものだということがわかる。

今回作るのはセッション毎に情報を持つわけではないので、NavigationCmdletProviderクラスから派生させることにする。

PSTodoProvider.cs
using System;
using System.Management.Automation.Provider;
using System.Diagnostics;

namespace PSTodo {
    /// <summary>
    /// Todo機能を提供するプロバイダクラス
    /// </summary>
    [CmdletProvider("Todo", ProviderCapabilities.ShouldProcess)]
    public class TodoProvider : NavigationCmdletProvider {
        public TodoProvider() { }

        protected override bool IsValidPath(string path) {
            return true;
        }
    }
}

実装する必要のある抽象メンバは「IsValidPath」メソッドだけなので、とりあえずこれを実装する。これはおそらく引数で渡されたパスが有効なパスかどうかを判断するためのメソッドだと思うので、とりあえずtrueを返しておく。

あと、Providerとして登録するにはCmdletProviderAttribute 属性でクラスをマークする必要があるので、Provider名を「Todo」としてマークしておく。二つ目の引数はよくわからないので、とりあえずProviderCapabilities.ShouldProcess を指定しておく(他のProviderもたいがいこれだったので)。

ここまでで一度PowerShellにこのProviderを登録してみる。
PowerShellに独自のCmdletやProviderを登録するにはPSSnapIn クラスから派生したインストーラクラスを作る必要がある。

この辺のことは以下のサイトを参考にした

TodoSnapIn.cs
using System;
using System.Management.Automation;
using System.ComponentModel;
using System.Diagnostics;

namespace PSTodo.Configuration {
    /// <summary>
    /// SnapInをインストールするクラス
    /// </summary>
    [RunInstaller(true)]
    public class TodoSnapIn : PSSnapIn {
        public TodoSnapIn() { }

        public override string Description {
            get { return "PowerShellにTodo機能を追加します。"; }
        }
        public override string Name {
            get { return "PSTodo"; }
        }
        public override string Vendor {
            get { return "coma2n"; }
        }
    }
}

Description、Name、Vendorというプロパティを実装すればOK。

あとはこのプロジェクトをビルドしてできたdllを「installutil.exe」を使ってPowerShellに登録する。

PS > installutil PSTodo.dll

これでPowerShellへの登録は完了。

PowerShellを起動して、以下のコマンドを実行する。

PS > Add-PSSnapIn PSTodo
PS > New-PSDrive Todo Todo "c:\PSTodo"

Add-PSSnapIn コマンドレットでPSTodoのスナップインを現在のセッションに追加する。
New-PSDrive コマンドレットでTodoというドライブ名でTodoプロバイダを「c:\PSTodo」というフォルダをルートにして、新しくドライブをマウントする。

Todoドライブが作れたので、さっそくcdをしてみると、

Set-Location : プロバイダがこの操作をサポートしていないため、プロバイダの実行が中止されました。
発生場所 行:1 文字:3
+ cd  <<<< todo:

というエラーが表示された。そりゃ、そんな簡単には作れないよね。