カスタムプロバイダーを作ってみよう その2

前回の続きです。

今回はまず、自作のプロバイダーでcd ができるようにする、つまりSet-Locationコマンドレットに対応させます。

その前にこのプロバイダーをデバッグできるようにしておきましょう。

デバッグの設定

Visual Studioのプロジェクトのプロパティを開いて、「デバッグ」タブの「開始動作」を「外部プログラムの開始」に設定します。そして外部プログラムとして「C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe」(WindowsXPの場合)を指定します。

この時点でPowerShellデバッグは可能になりますが、自作のプロバイダーが読み込まれないので、読み込まれるように設定します。

「開始オプション」の「コマンドライン引数」に以下のコマンドを指定します。

-NoExit -Command Add-PSSnapin Hoge; [void](New-PSDrive Hoge Hoge c:\temp)

これでデバッグを開始するとプロバイダ−のデバッグが可能になります。

Set-Locationコマンドレットへの対応

Set-Locationコマンドレットに対応させるには、以下の二つのメソッドをオーバーライドします。

  • bool ItemExists(string path)
  • bool IsItemContainer(string path)

Set-Locationコマンドレットが実行されると、まずプロバイダーのItemExistsメソッドが呼び出されてそのパスが存在するかどうかを問い合わされます。

そして、そのパスが存在すればIsItemContainerメソッドが呼び出されて、そのパスがコンテナかどうか、つまり中に入れるかどうかが問い合わされます。

つまりこの二つのメソッドがtrueを返せば、そのパスには移動が可能ということになります。

では、さっそく実装してみましょう。

HogeProvider.cs
using System;
using System.Management.Automation.Provider;

namespace PSHoge {
     [CmdletProvider("Hoge", ProviderCapabilities.ShouldProcess)]
     public class HogeProvider : NavigationCmdletProvider {

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

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

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

とりあえず、両方のメソッドともtrueを返しておきます。

試しにデバッグ実行をして、Hogeドライブにcdをしてみて下さい。以下のようにHogeドライブに移動できるはずです。

PS > cd Hoge:
PS Hoge:\>

このようにしてSet-Locationコマンドレットに対応します。もちろん実際には問い合わせられたパスが存在するかどうかを調べてその結果を返す必要があります。

重要なのはパスが存在するかどうかを調べる方法がプロバイダーに移譲されている事です。
こうすることでそのパスがファイルシステムであったり、例えばFTPのアドレスであったり、仮想的なリソースであることを許容できます。

では、次はdirコマンド、つまりGet-ChildItemコマンドレットに対応させます。

Get-ChildItemコマンドレットへの対応

Get-ChildItemコマンドレットに対応させるには、上述の二つのメソッドに加えて以下のメソッドをオーバーライドします。

  • void GetChildItems(string path, bool recurse)

Get-ChildItemsコマンドレットが実行されると、Set-Locationコマンドレットの場合と同様にそのパスが存在する事とコンテナであることが問い合わされ、そしてGetChildItemsメソッドでアイテムの一覧が列挙されます。

とりあえず実装してみましょう。

HogeProvider.cs
using System;
using System.Management.Automation.Provider;

namespace PSHoge {
     [CmdletProvider("Hoge", ProviderCapabilities.ShouldProcess)]
     public class HogeProvider : NavigationCmdletProvider {

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

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

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

         protected override void GetChildItems(string path, bool recurse) {
         }
     }
}

なにもしていません。

この状態でデバッグ実行して、dirコマンドを実行すると何も表示されませんがエラーも出ません。

では、画面に何か出力してみましょう。画面への出力、正確にはパイプラインへの出力を行うにはWriteItemObjectメソッドを使用します。FileSystemProviderではFileInfoDirectoryInfoがパイプラインに出力されます。今回は独自のクラスを作ってそのオブジェクトを出力することにします。

HogeFile.cs
using System;
using System.Diagnostics;

namespace PSHoge {
     [Serializable]
     [DebuggerStepThrough]
     public class HogeFile {
         public string FileName {
             get;
             set;
         }
         public long Size {
             get;
             set;
         }
     }
}

ファイル名とファイズサイズを保持する「HogeFile」というクラスを定義しました。

では、カレントディレクトリのファイルを調べて、このオブジェクトを出力する処理を書いてみましょう。

protected override void GetChildItems(string path, bool recurse) {
     foreach(var f in Directory.GetFiles(path)) {
         var fi = new FileInfo(f);
         base.WriteItemObject(new HogeFile {
             FileName = fi.Name,
             Size = fi.Length
         }, f, false);
     }
}

渡されたパスに存在するファイルを列挙して、HogeFileオブジェクトをWriteItemObjectメソッドで出力しています。

デバッグ実行して、dirコマンドを実行すると以下のように表示されます(もちろんc:\tempフォルダにいくつかのファイルがある前提です)。

PS Hoge:\> dir

PSPath        : Hoge\Hoge::c:\temp\100.txt
PSParentPath  : Hoge\Hoge::c:\temp
PSChildName   : 100.txt
PSDrive       : Hoge
PSProvider    : Hoge\Hoge
PSIsContainer : False
FileName      : 100.txt
Size          : 6

PSPath        : Hoge\Hoge::c:\temp\200.txt
PSParentPath  : Hoge\Hoge::c:\temp
PSChildName   : 200.txt
PSDrive       : Hoge
PSProvider    : Hoge\Hoge
PSIsContainer : False
FileName      : 200.txt
Size          : 10

PSPath        : Hoge\Hoge::c:\temp\300.txt
PSParentPath  : Hoge\Hoge::c:\temp
PSChildName   : 300.txt
PSDrive       : Hoge
PSProvider    : Hoge\Hoge
PSIsContainer : False
FileName      : 300.txt
Size          : 14

FileNameプロパティとSizeプロパティがあるのが確認できますが、他にも「PS」から始まるプロパティが複数あります。これらはNavigationCmdletProviderが自動的に付加するものです。

このままでは見栄えが悪いので、出力されるオブジェクトの書式設定をします。 オブジェクトの書式設定については以前書いた以下の記事を参考にして下さい。

以下がオブジェクトの書式設定ファイルです。

pshoge.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
      <SelectionSets>
          <SelectionSet>
              <Name>PSHogeTypes<Name>
              <Types>
                  <TypeName>PSHoge.HogeFile<TypeName>
              <Types>
          <SelectionSet>
      <SelectionSets>

      <ViewDefinitions>
         <View>
              <Name>children<Name>
              <ViewSelectedBy>
                  <SelectionSetName>PSHogeTypes<SelectionSetName>
              <ViewSelectedBy>
              <TableControl>
                  <TableHeaders>
                      <TableColumnHeader>
                         <Label>ファイル名<Label>
                      <TableColumnHeader>
                      <TableColumnHeader>
                         <Label>ファイルサイズ<Label>
                      <TableColumnHeader>
                  <TableHeaders>
                  <TableRowEntries>
                      <TableRowEntry>
                          <TableColumnItems>
                              <TableColumnItem>
                                  <PropertyName>FileName<PropertyName>
                              <TableColumnItem>
                              <TableColumnItem>
                                  <PropertyName>Size<PropertyName>
                              <TableColumnItem>
                          <TableColumnItems>
                      <TableRowEntry>
                  <TableRowEntries>
              <TableControl>
          <View>
      <ViewDefinitions>
<Configuration>

このオブジェクト書式設定ファイルを読み込んで、再度dirコマンドを実行すると以下のように表示されます。

PS Hoge:\>

ファイル名                                        ファイルサイズ
----------                                        --------------
100.txt                                           6
200.txt                                           10
300.txt                                           14

このようにしてGet-ChildItemコマンドレットに対応させます。

次はNew-Itemコマンドレットやタブ補間などに対応させてみます。