前回の続きです。

今回はSet-Contentコマンドレットへの対応からはじめます。

 

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

Set-Contentコマンドレットに対応するには、IContentCmdletProviderインターフェースの以下のメソッドを実装します。

  • void ClearContent
  • IContentWriter GetContentWriter(string path)
  • object GetContentWriterDynamicParameters(string path)

Set-Contentコマンドレットはファイルの内容を置き換える為、GetContentWriterメソッドが呼び出される前にClearContentメソッドが呼び出されて、コンテントの内容をクリアするようになっています。 なのでClearContentメソッドを実装する必要があります。

まずはIContentWriterインターフェースを実装したクラスを定義します。

IContentWriterインターフェースには以下のメソッドが定義されています。

  • void Close()
  • void Seek(long offset, SeekOrigin origin) 
  • IList Write(IList content)

まずは、このインターフェースを実装したクラスを定義します。

using System;
using System.IO;
using System.Text;
using System.Management.Automation.Provider;
using System.Collections;

namespace PSHoge {
     public class HogeFileContentWriter : IContentWriter {
         private StreamWriter sw;

         public HogeFileContentWriter(string path, Encoding encoding) {
             sw = new StreamWriter(path, true, encoding);
         }
         ~HogeFileContentWriter() {
             Dispose();
         }

         public void Close() {
             if(sw != null) {
                 sw.Close();
                 sw = null;
             }
         }
         public void Seek(long offset, SeekOriginorigin) {
             throw new NotImplementedException();
         }
         public IList Write(IList content) {
             foreach(var c in content) {
                 sw.WriteLine(c.ToString());
             }
             return null;
         }

         public void Dispose() {
             Close();

             GC.SuppressFinalize(this);
         }
     }
}

コンストラクタでファイル名とエンコードを指定し、ファイルを追記モードで開いています。

Writeメソッドではリストで渡された要素の数だけファイルに内容を出力しています。 あとはお馴染みですね。

今回は始めから動的パラメータも定義しておきます。

[Serializable]
[DebuggerStepThrough]
public class SetContentParameters : FileSystemContentWriterDynamicParameters {
}

FileSystemContentWriterDynamicParametersクラスから派生しておくと楽ができます。

ClearContent、GetContentWriterメソッドを実装します。

public void ClearContent(string path) {
     if(File.Exists(path)) File.Delete(path);
}

public IContentWriter GetContentWriter(string path) {
     var scParams = base.DynamicParameters as SetContentParameters;
     var encoding = scParams != null ? scParams.EncodingType : Encoding.UTF8;

     return new HogeFileContentWriter(path, encoding);
}

public object GetContentWriterDynamicParameters(string path) {
     return new SetContentParameters();
}

ClearContentメソッドではファイルを削除しています。

GetContentWriterメソッドではHogeFileContentWriterクラスをインスタンス化して返しておきます。

デバッグ実行して、以下のように動作を確認できます。

PS Hoge:\> Set-Content 400.txt hoge

これでSet-Contentコマンドレットに対応できました。

 

まとめ

ここまでカスタムプロバイダーの作り方について説明してきましたが、いかかでしょうか?だいたいイメージは掴めたのではないでしょうか。

カスタムプロバイダーを作るための手順をまとめておきます。

  1. NavigationCmdletProviderクラスから派生して、プロバイダークラスを作る。
    1. IsValidPathメソッドをオーバーライドする。
    2. Set-LocationできるようにItemExistsIsItemContainerメソッドをオーバーライドする。
    3. 各コマンドレットに対応したメソッドをオーバーライドする。
      Get-ChildItem
      コマンドレットならGetChildItemsメソッド
      New-ItemコマンドレットならNewItemメソッドという具合
  2. PSSnapInクラスから派生してインストーラクラスを作る。
  3. InstallUtil.exeを使ってアセンブリをPowerShellにインストールする。
  4. Add-PSSnapinコマンドレットを使ってスナップインを追加する。
  5. New-PSDriveコマンドレットでカスタムプロバイダーのPSDriveを追加する。

といった感じです。

今回説明した以外にもNew-PSDriveに対応するNewDriveメソッドをオーバーライドして、PSDriveを追加する時の条件を変更できたりもします。

他にもパスの区切り記号の変更などもできたりもします(デフォルトでは円記号)。

カスタムプロバイダーを作る上で最も難しい事はどのようなデータソースをプロバイダーで抽象化するかということでしょう。

例えば、SQL Serverであったり、FTPサイトであったり、WebDAV、Amazon S3など色々と考える事ができます。結局のところはアイデア勝負ということです。

何か抽象化できそうなデータソースがあれば、試しにカスタムプロバイダーを作ってみることをお勧めします。

それでは楽しいPowerShellライフを!!

 

ソース


目次


Posted in:   Tags:

最新のブログ

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

前回の続きです。

今回はタブ補完から実装します。

 

タブ補完への対応

タブ補完に対応させるには、以下のメソッドをオーバーライドします。

  • void GetChildNames(string path, ReturnContainers returnContainers)

実装する前にタブ補完とはどのような機能なのかということを説明しておきます。

PowerShellでcdやremなどのコマンドを実行する時に、その引数としてファイル名などのパスを渡す必要がありますが、その時パスを全て入力するのではなく、例えば「100.txt 」を入力する時に「1」だけ入力してTabキーを押すと自動的に補完してくれます。再度Tabキーを押すと次の候補を表示してくれる、という機能がタブ補完です。

PS c:\> cd W

Tabキーを押すと、

PS c:\> cd WINDOWS

補完される。

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

protected override void GetChildNames(string path, ReturnContainers returnContainers) {
     foreach(var f in Directory.GetFiles(path)) {
         base.WriteItemObject(Path.GetFileName(f), f, false);
     }
}

やる事は単純です。渡されたパスに存在するファイルの一覧を列挙して、ファイル名だけをパイプラインに出力します。これだけです。

こうすることで、タブ補完の候補がPowerShellに渡される事になります。後はPowerShell側が勝手にやってくれます。

デバッグ実行して、動作を試してみて下さい。何故かこのメソッドは二回呼び出されますが気にしないでおきましょう。

 

まだ実装していない機能で、

  • Set-Item
  • Get-Item
  • Rename-Item 
  • Copy-Item

などがありますが、これらはほとんど今までと同じ方法で実装できるのでこれ以上の説明はしません。

なので、他とは少し変わった機能であるGet-ContentSet-Contentコマンドレットを実装してみます。

 

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

Get-Contentコマンドレットに対応するにはプロバイダーはIContentCmdletProviderインターフェースを実装する必要があります。

IContentCmdletProviderインターフェースには以下のメソッドが定義されています。

  • void ClearContent(string path) 
  • object ClearContentDynamicParameters(string path)
  • IContentReader GetContentReader(string path)
  • object GetContentReaderDynamicParameters(string path)
  • IContentWriter GetContentWriter(string path)
  • object GetContentWriterDynamicParameters(string path)

動的パラメータのありなしをペアとして三つのメソッドがあります。

この中でGet-Contentの為に必要なのはGetContentReaderメソッドとそれの動的パラメータ版です。

GetContentReaderメソッドはIContentReaderインターフェースを実装したオブジェクトを返す必要があります。

IContentReaderインターフェースには以下のメソッドが定義されています。

  • void Close() 
  • IList Read(long readCount)
  • void Seek(long offsert, SeekOrigin origin)
  • void Dispose()

なんとなくストリームっぽい感じですね。それぞれの役割はだいたいわかるでしょう。

とりあえずこのインターフェースを実装したクラスを作ってみます。

public class HogeFileContentReader : IContentReader {

     private StreamReader sr;
     
     public HogeFileContentReader(string path) {
         sr = File.OpenText(path);
     }
     ~HogeFileContentReader() {
         Dispose();
     }

     public void Close() {
         if(sr != null) {
             sr.Close();
             sr = null;
         }
     }
     public IList Read(long readCount) {
         var lines = new List<string>();

         for(var i = 0; i < readCount; i++) {
             var line = sr.ReadLine();

             if(line == null) break;

             lines.Add(line);
         }
         return lines;
     }
     public void Seek(long offset, SeekOrigin origin) {
         throw new NotImplementedException();
     }

     public void Dispose() {
         Close();

         GC.SuppressFinalize(this);
     }
}

コンストラクタで引数にパスを指定してファイルを読み込み、ReadメソッドでreadCountの分だけファイルを読み込んで、その結果をリストで返しています。後はお馴染みの処理ですね。

あとはこのクラスをGetContentReaderメソッドでインスタンス化して返すだけです。

public IContentReader GetContentReader(string path) {
     return new HogeFileContentReader(path);
}

public object GetContentReaderDynamicParameters(string path) {
     return null;
}

動的パラメータはいらないのでnullを返しておきます。

これでデバッグ実行して、以下のコマンドを実行してみて下さい。

PS Hoge:\> Get-Content 300.txt

これでファイルの中身が出力されるはずです。

ちなみにこのままではUTF8のファイルしか正常に表示できないので、動的パラメータでエンコードを指定できるようにしておきましょう。

まずはパラメータクラスを定義します。

[Serializable]
[DebuggerStepThrough]
public class GetContentParameters : FileSystemContentReaderDynamicParameters {
}

Get-Contentの動的パラメータクラスはFileSystemContentReaderDynamicParametersクラスから派生すると便利です。Encodingプロパティも既に定義されています。

このクラスのインスタンスを返すようにGetContentReaderDynamicParametersメソッドを変更しておきます。

public object GetContentReaderDynamicParameters(string path) {
     return new GetContentParameters();
}

HogeFileContentReaderのコンストラクタもエンコードを指定できるように変更しておきます。

public HogeFileContentReader(string path, Encoding encoding) {
     sr = new StreamReader(path, encoding);
}

GetContentReaderメソッドも動的パラメータを取得するように変更しておきます。

public IContentReader GetContentReader(string path) {
     var gcParams = base.DynamicParameters as GetContentParameters;
     var encoding = gcParams != null ? gcParams.EncodingType : Encoding.UTF8;

     return new HogeFileContentReader(path, encoding);
}

これでGet-Contentコマンドレットでエンコードを指定できるようになりました。

PS Hoge:\> Get-Content -Encoding UTF8 300.txt 

Get-Contentの説明が長くなってしまったので、Set-Contentは次回にします。

 

目次


Posted in:   Tags:

最新のブログ

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

前回の続きです。

今回はNew-Itemコマンドレットへの対応から始めます。

 

New-Itemコマンドレットへの対応

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

  • void NewItem(string path, string itemTypeName, object newItemValue) 

FileSystemProviderでNew-Itemコマンドレットを使う場合を考えてみましょう。

PS c:\> New-Item 100.txt File -itemType File -value Hello

ファイル名とitemTypeに「File」か「Directory」、valueにファイルの内容を指定します。

これらのパラメータがNewItemメソッドのそれぞれの引数に対応する事は明白ですね。それでは実装してみましょう。

protected override void NewItem(string path, string itemTypeName, object newItemValue) {
     using(var fs = File.Create(path)) {
         base.WriteItemObject(new HogeFile {
             FileName = Path.GetFileName(path),
             Size = fs.Length

         }, path, false);
     }
}

渡されたパスでファイルを作って、その情報をHogeFileオブジェクトとしてパイプラインに出力しています。

デバッグ実行して、動作をテストしてみましょう。

PS Hoge:\> New-Item 400.txt
ファイル名                                        ファイルサイズ
----------                                        --------------
400.txt                                           0

itemTypeNamenewItemValueへの対応はやめておきます。その代わり新しいパラメータを定義してみます。

例えば、以下のように-BufferSizeというパラメータで数値を指定すると、

PS Hoge:\>New-Item 400.txt -BufferSize 128
ファイル名                                        ファイルサイズ
----------                                        --------------
400.txt                                           128

そのサイズでファイルが作成されるというものです。

こういった新しいパラメータを定義するためのDynamicParametersという仕組みがプロバイダーには用意されています。

例えばNew-Itemコマンドレットに新しいパラメータを定義するには、NewItemDynamicParametersというメソッドをオーバーライドします。

このメソッドのシグネチャを見てみると、

  • object NewItemDynamicParameters(string path, string itemTypeName, object newItemValue) 

となっていますが、引数はNewItemメソッドと同じで返り値がobject型になっています。このメソッドはNewItemメソッドが呼び出される前に必ず呼び出されるようになっています。

では、このメソッドの返り値として何を返せばいいのでしょうか?

答えは以下のようなパラメータをプロパティとして持つプレーンなクラスを定義して、そのインスタンスを返します。

[Serializable]
[DebuggerStepThrough]
public class NewItemParameters {
     [Parameter]
     public int BufferSize {
         get;
         set;
     }
}

パラメータとして定義するプロパティにはParameter属性でマークをしておきます。

では、このクラスのインスタンスを返すようにNewItemDynamicParametersメソッドを実装しておきます。

protected override object NewItemDynamicParameters(string path, string itemTypeName, object newItemValue) {
     return new NewItemParameters();
}

パラメータを定義したので、NewItemメソッドもこれに対応できるように変更しておきます。

protected override void NewItem(string path, string itemTypeName, object newItemValue) {
     var niParams = base.DynamicParameters as NewItemParameters;
     var bufferSize = niParams != null ? niParams.BufferSize : 0;

     using(var fs = File.Create(path, bufferSize)) {
         fs.Write(new byte[bufferSize], 0, bufferSize);

         base.WriteItemObject(new HogeFile {
             FileName = Path.GetFileName(path),
             Size = fs.Length

         }, path, false);
     }
}

動的パラメータはDynamicParametersプロパティで取得できます。動的パラメータが定義されていれば、BufferSizeを取得して、そのサイズのファイルを作成しています。

これでデバッグ実行すると前述のように動作するはずです。

このようにして既存のコマンドレットに新しいパラメータを追加する仕組みがプロバイダーには用意されています。おもしろいですね!!

ファイル作成ができるようになったので、その逆ファイルの削除もできるようにしておきましょう。

 

Remove-Itemコマンドレットへの対応

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

  • void RemoveItem(string path, bool recurse)
  • bool HasChildItems(string path)

RemoveItemメソッドはそのままですが、HasChildItemsメソッドはどういう役割でしょうか?
このメソッドはアイテムを削除する時にそのアイテムに子アイテムがあるかどうかを問い合わせます。このメソッドがfalseを返せばアイテムは削除されますが、trueを返せばRemove-Itemコマンドレットで-forceパラメータが指定された場合のみ削除されます。

これはすごく単純なのでさっさと実装します。

protected override bool HasChildItems(string path) {
     return false;
}

protected override void RemoveItem(string path, bool recurse) {
     File.Delete(path);
}

これだけです。

デバッグ実行して、以下のコマンドを実行するとファイルが削除されるはずです。

PS Hoge:\> Remove-Item 400.txt 

次回はタブ補完を実装します。

 

目次


Posted in:   Tags:

現在のレート 5.0 (1人)

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

前回の続きです。

今回はまず、自作のプロバイダーで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を返せば、そのパスには移動が可能ということになります。

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

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メソッドでアイテムの一覧が列挙されます。

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

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がパイプラインに出力されます。今回は独自のクラスを作ってそのオブジェクトを出力することにします。

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が自動的に付加するものです。

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

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

<?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コマンドレットやタブ補間などに対応させてみます。

 

目次


Posted in:   Tags:

最新のブログ

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

PowerShellの重要な特徴として、ファイルシステムやレジストリ、環境変数など異なるデータソースに対して共通の操作(コマンドレット)が行えるということが挙げられます。

例えば、以下のように「C」ドライブ直下に「New-Item」コマンドレットでファイルを作成できるのと同じ感覚で、

PS c:\> New-Item hoge

「HKLM」ドライブ(HKEY_LOCAL_MACHINE)に移動して、「New-Item」コマンドレットでレジストリキーが作れるといった具合にです。

PS HKLM:\Software > New-Item hoge

これらはプロバイダーという機構によって成り立っています。

ファイルシステムならFileSystemProvider、レジストリならRegistryProvider、環境変数ならEnvironmentProviderと言った具合にそれぞれに対応するプロバイダーが用意されています。

PowerShellには「New-PSDrive」というコマンドレットが用意されています。
このコマンドレットは「PSDrive」というPowerShell上でだけ有効な仮想的なドライブを新規作成するものですが、このコマンドレットのパラメータを見て下さい。

New-PSDrive [-name]  [-psProvider]  [-root]  [-description ] [-scope ] [-credential ]

二つ目の引数に「psProvider」というのがあります。この引数でどのプロバイダーを使ってPSDriveを作成するかを決めるわけです。

例えばFileSystemProviderを使って、「C:\WINDOWS」ディレクトリをルートとする「WIN」ドライブを作る場合、以下のようにします。

PS > New-PSDrive WIN FileSystem C:\WINDOWS
Name       Provider      Root
----       --------      ----
WIN        FileSystem    C:\WINDOWS

このようにして、プロバイダーを指定してPSDriveを作成する事ができます。

これらの事からプロバイダーとは、様々な種類のPSDriveを提供するための機構だということがわかります。

 

開発環境の構築

では、さっそくプロバイダーを作っていきましょう。

開発環境として、

  • PowerShell 1.0
  • Microsoft Visual Studio 2008 Standard Edition (Visual C# 2008 Expressでも可) 

を使用します。

また、プロバイダーの開発には.NET FrameworkとC#に対する基本的な知識が必要になります。

開発用のアセンブリをコピー

プロバイダーの開発には、「System.Management.Automation」というアセンブリが必要になりますが、このアセンブリはGACにのみインストールされているので、ここから取り出す必要があります。

GACにインストールされたアセンブリはWindows Explorerからはコピーができないので、コマンドプロンプトかPowerShellを使ってコピーする必要があります。

「System.Management.Automation.dll」を以下のパスから適当な場所にコピーして下さい。

  • C:\WINDOWS\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35\System.Management.Automation.dll

 

プロジェクトの作成

では、プロバイダーの仕組みを学ぶ為に簡単なプロバイダーを作ってみましょう。

Visual Studio 2008を起動して、言語は「Visual C#」で「クラスライブラリ」プロジェクトを作成します。プロジェクト名は「PSHoge」とします。



まずは先程コピーした「System.Management.Automation.dll」アセンブリを参照設定に追加しておきます。

では、プロバイダーを作ってみましょう。

プロバイダーはCmdletProviderクラスを継承して作ります。
しかし、CmdletProviderには必要最低限の機能しか実装されていないので、ここからプロバイダーを実装するのは結構大変です。

なので、通常はCmdletProviderから派生したNavigationCmdletProviderを使用します。

CmdletProviderNavigationCmdletProviderなどとの継承関係を以下に示します。

プロバイダークラスの継承ツリー

  • CmdletProvider
    • DriveCmdletProvider
      • ItemCmdletProvider
        • ContainerCmdletProvider
          • SessionStateProviderBase
            • AliasProvider
            • EnvironmentProvider
            • FunctionProvider
            • VariableProvider
          • NavigationCmdletProvider
            • FileSystemProvider
            • RegistryProvider

ファイルシステムやレジストリはNavigationCmdletProviderから、環境変数やエイリアスはSessionStateProviderBaseから派生しているのがわかります。

では、NavigationCmdletProviderを継承してプロバイダークラスを作ります。

「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;
         }

     }
}

NavigationCmdletProviderクラスを継承して、IsValidPathメソッドをオーバーライドします。ここではとりあえずtrueを返しておきます。

そして、プロバイダークラスである事を示す為にCmdletProvider属性でマークしています。引数として「Hoge」と「ProviderCapabilities.ShouldProcess」を渡しています。

前者はプロバイダー名で後者はとりあえずこれを指定しておいて下さい。

次にこのプロバイダーをPowerShellにインストールするためのインストーラクラスを作成します。

using System;
using System.Management.Automation;
using System.ComponentModel;

namespace PSHoge {
     [RunInstaller(true)]
     public class HogeSnapIn : PSSnapIn {

         public override string Description {
             get { return "テスト用のプロバイダーです。"; }
         }

         public override string Name {
             get { return "Hoge"; }
         }

         public override string Vendor {
             get { return "coma2n"; }
         }
     }
}

PowerShellのインストーラクラスはPSSnapInクラスから派生させます。DescriptionNameVendorというプロパティをそれぞれ実装する必要があります。

そして、このクラスがインストーラクラスである事を示す為にRunInstaller属性でマークしておきます。

とりあえずここまでで一度PowerShellにこのプロバイダーをインストールしてみましょう。

プロジェクトをビルドして、プロジェクトの出力ディレクトリ(bin\Debug)にコマンドプロンプトで移動して下さい。

以下のコマンドを実行してプロバイダーをインストールします。

PS > InstallUtil.exe PSHoge.dll

正常にインストールが完了したらPowerShellを起動して以下のコマンドを実行します。

PS > Add-PSSnapin Hoge

これでPowerShellにプロバイダーのインストールが完了しました。

あとは前述の「New-PSDrive」コマンドレットを使って、ドライブを新規作成します。

PS > New-PSDrive Hoge Hoge c:\

これで「Hoge」プロバイダーで「Hoge」ドライブを作る事ができました。ルートディレクトリのパスはここでは意味はありませんが、指定する必要があります。

では、試しに「Hoge」ドライブにcdしてみましょう。

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

実はこの段階ではまだcdはできません。では次はこれができるようにしてみましょう。

 

目次


Posted in:   Tags:

現在のレート 5.0 (1人)

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

前回の続きです。

今回はまずオブジェクト書式設定ファイルの各要素について説明します。

myfilesystem.format.ps1xml

1:   <?xml version="1.0" encoding="utf-8" ?>
2:   <Configuration>
3:        <SelectionSets>
4:            <SelectionSet>
5:                <Name>FileSystemTypes<Name>
6:                <Types>
7:                    <TypeName>System.IO.DirectoryInfo</TypeName>
8:                    <TypeName>System.IO.FileInfo</TypeName>
9:                </Types>
10:           </SelectionSet>
11:       </SelectionSets>
12:  
13:       <ViewDefinitions>
14:          <View>
15:               <Name>children</Name>
16:               <ViewSelectedBy>
17:                   <SelectionSetName>FileSystemTypes</SelectionSetName>
18:               </ViewSelectedBy>
19:               <TableControl>
20:                   <TableHeaders>
21:                       <TableColumnHeader />
22:                   <TableHeaders>
23:                   <TableRowEntries>
24:                       <TableRowEntry>
25:                           <TableColumnItems>
26:                               <TableColumnItem>
27:                                   <PropertyName>Name</PropertyName>
28:                               </TableColumnItem>
29:                           </TableColumnItems>
30:                       </TableRowEntry>
31:                   </TableRowEntries>
32:               </TableControl>
33:           </View>
34:       </ViewDefinitions>
35:  </Configuration>

1行目はこのファイルがXML文書であることを表す宣言なので必ず必要になります。

2行目から「Configuration」というルート要素が始まります。この下にオブジェクトの書式を記述していくことになります。

3行目からは「SelectionSets」という要素があり、これは「SelectionSet」をまとめるための要素になります。

SelectionSet

「SelectionSet」要素は書式を設定する型を指定するための要素です。

設定できる要素として、

  • Name
  • Types
    • TypeName

があります。

「Name」要素ではSelectionSetの名前(識別子)を指定します。

「Types」要素は「TypeName」をまとめる要素です。「TypeName」要素には型名を指定します。

ここでは「System.IO.FileInfo」型と「System.IO.DirectoryInfo」型を書式設定の対象の型として、「FileSystemTypes」という識別子を設定しています。

13行目からは「ViewDefinitions」という要素があり、これは「View」要素をまとめる要素になります。

View

「View」要素は実際の書式設定を指定するための要素です。

設定できる要素として、

  • Name
  • ViewSelectedBy
    • SelectionSetName
  • TableControl

があります。

「Name」要素ではViewの名前を指定します。

「ViewSelectedBy」要素は「SelectionSetName」をまとめる要素です。「SelectionSetName」要素にはこの書式設定を適用する対象のSelectionSetの名前を指定します。
ここでは、先程定義してあった「FileSystemTypes」が指定されています。

「TableControl」要素はオブジェクトをテーブル状に書式設定するための指定を行うための要素です。この他に「ListControl」というリスト状に書式設定する要素も用意されています。

では、もう一度このオブジェクト書式設定ファイルを適用したことによって、コンソールへの出力がどのように変更されたか見てみましょう。

PS > dir

Name ---- Documents and Settings Inetpub Program Files temp user WINDOWS AUTOEXEC.BAT CONFIG.SYS

「Name」という列だけが表示されています。

これは26行目から28行目で「TableColumnItem」要素で「PropertyName」に「Name」と指定されているからです。

では、これ以外のプロパティを表示するにはどうすればいいでしょうか?それには単純に以下のようにします。

19:               <TableControl>
20:                   <TableHeaders>
21:                       <TableColumnHeader />
22:                       <TableColumnHeader />
23:                   <TableHeaders>
24:                   <TableRowEntries>
25:                       <TableRowEntry>
26:                           <TableColumnItems>
27:                               <TableColumnItem>
28:                                   <PropertyName>Name</PropertyName>
29:                               </TableColumnItem>
30:                               <TableColumnItem>
31:                                   <PropertyName>Length</PropertyName>
32:                               </TableColumnItem>
33:                           </TableColumnItems>
34:                       </TableRowEntry>
35:                   </TableRowEntries>
36:               </TableControl>

22行目に「TableColumnHeader」要素を追加しています。この要素は表示する列(プロパティ)の数だけ必要になります。

30行目から32行目に「TableColumnItem」要素を追加し、「PropertyName」に「Length」と指定しています。

この書式設定を適用すると出力は以下のようになります。

PS > dir

Name Length ---- ------ Documents and Settings Inetpub Program Files temp user WINDOWS AUTOEXEC.BAT 0 CONFIG.SYS 0

「Length」という列が追加されているの確認できるはずです。

次は列名が英語で表示されているので、これを日本語に変えてみます。

20:                    <TableHeaders>
21:                        <TableColumnHeader>
22:                            <Label>名前<Label>
23:                        <TableColumnHeader>
24:                        <TableColumnHeader>
25:                            <Label>ファイルサイズ<Label>
26:                        <TableColumnHeader>
27:                    <TableHeaders>

列名を変更するには「TableColumnHeader」要素に「Label」要素を指定します。

このようにして、オブジェクト書式設定ファイルを使うとオブジェクトの表示形式をある程度自由にカスタマイズすることができます。

この他にも色々なカスタマイズが可能なので詳しくは以下のURLの記事を参考にして下さい。


Posted in: PowerShell  Tags:

最新のブログ

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

今回はオブジェクトの書式設定についてです。

コンソールから「dir 」などの何かしらアウトプットのあるコマンドを実行すると、以下のように出力結果(オブジェクト)が整形されて表示されると思います。

PS > dir

ディレクトリ: Microsoft.PowerShell.Core\FileSystem::C:\
Mode LastWriteTime Length Name ---- ------------- ------ ---- d---- 2007/07/11 15:10 Documents and Settings d---- 2008/02/13 16:24 Inetpub d-r-- 2008/07/15 13:59 Program Files d---- 2008/06/24 10:35 temp d---- 2007/11/05 14:14 user d---- 2008/07/15 9:03 WINDOWS -a--- 2006/10/04 19:02 0 AUTOEXEC.BAT -a--- 2006/10/04 19:02 0 CONFIG.SYS -a--- 2008/01/24 10:54 0 _dummy

基本的にここで表示されている属性は出力されたオブジェクトのプロパティになります。

「dir」コマンドであれば出力されるオブジェクトの型は「FileInfo」か「DirectoryInfo」になるので、「LastWriteTime」や「Length」、「Name」といったプロパティがこれらの型に存在するということになります(Modeはプロパティではありませんが、ここでは詳細は割愛します)。

もちろんこれらの型には他にも色々なプロパティが存在しますが、何故表示されるのが「LastWriteTime」や「Length」などの限られたプロパティだけなのでしょうか?

これにはちゃんと理由があります。PowerShellでは(.NETの)型毎に 表示形式(書式)を設定できるようになっているからです。

この設定はXML形式のファイルで記述するもので「オブジェクト書式設定ファイル」 と呼ばれます。拡張子は「.ps1xml」です。


実習

では、実際にこの「オブジェクト書式設定ファイル」を作って「dir」コマンドで出力される結果をカスタマイズしてみましょう。

といっても、このファイルは非常に冗長な記述が必要なので一から記述するのは手間がかかります。なので、初めから用意されている書式ファイルをコピーして使いましょう。

PowerShellのインストールディレクトリにある「filesystem.format.ps1.xml」というファイルを開いて下さい。

PowerShellのインストールディレクトリは、以下のようにすると確認できます。

PS > $PSHOME
C:\WINDOWS\system32\WindowsPowerShell\v1.0

このファイルを適当な場所にコピーして、以下のように変更して下さい。

myfilesystem.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
     <SelectionSets>
         <SelectionSet>
             <Name>FileSystemTypes</Name>
             <Types>
                 <TypeName>System.IO.DirectoryInfo</TypeName>
                 <TypeName>System.IO.FileInfo</TypeName>
             </Types>
         </SelectionSet>
     </SelectionSets>

     <ViewDefinitions>
        <View>
             <Name>children</Name>
             <ViewSelectedBy>
                 <SelectionSetName>FileSystemTypes</SelectionSetName>
             </ViewSelectedBy>
             <TableControl>
                 <TableHeaders>
                     <TableColumnHeader />
                 </TableHeaders>
                 <TableRowEntries>
                     <TableRowEntry>
                         <TableColumnItems>
                             <TableColumnItem>
                                 <PropertyName>Name</PropertyName>
                             </TableColumnItem>
                         </TableColumnItems>
                     </TableRowEntry>
                 </TableRowEntries>
             </TableControl>
         </View>
     </ViewDefinitions>
</Configuration>

細かい説明は後にして、まずはこの書式設定ファイルを適用してみましょう。

コンソールから以下のコマンドを実行して下さい。

PS > Update-FormatData -PrependPath myfilesystem.format.ps1xml

何もメッセージが表示されなければ、適用に成功です。

ここで一度「dir」コマンドを 実行してみて下さい。以下のように出力結果が変更されているのが確認できるはずです。

PS > dir

Name ---- Documents and Settings Inetpub Program Files temp user WINDOWS AUTOEXEC.BAT CONFIG.SYS _dummy

では、先程読み込んだ書式設定ファイルの内容について説明していきます。

To be continued...


Posted in: PowerShell  Tags:

最新のブログ

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
山口 posted on 7月 11, 2008 14:23

今回はPowerShellの実行環境について、私が普段使っている環境を紹介します。
ここでいう環境とはPowerShellの起動時に読み込まれるプロファイルファイルの設定についてです。

プロファイルファイルとはユーザディレクトリ配下の「WindowsPowerShell\profile.ps1」というファイルのことです。
XPでは「c:\Documents and Settings\ユーザ名\My Documents\Windows PowerShell」というディレクトリに置かれています(無い場合は作って下さい)。

PowerShellは起動するとまずこのファイルに書かれたスクリプトを実行するので、よく使う変数や関数を事前に読み込んでおくことができます。

私の環境ではこのプロファイルファイルが置かれているディレクトリの構成を以下のようにしています。

ディレクトリ構成

  • formatディレクトリにはフォーマットファイル(.ps1.xml)を入れておきます。
  • libディレクトリには起動時に読み込むスクリプトファイルを入れておきます。
  • scriptディレクトリには(直接実行する)スクリプトファイルを入れておきます。

そして、プロファイルファイルではこれらのディレクトリからスクリプトを読み込んだり、パスを通すための設定を行います。

profile.ps1

# ライブラリフォルダ配下のファイルを読み込む
Get-ChildItem $LIB_HOME *.ps1 | % { .($_.FullName) }
# スクリプトフォルダ配下のフォルダに対してもパスを通す
Get-ChildItem $SCRIPT_HOME -recurse | ? { $_ -is [IO.DirectoryInfo] } | % { $Env:Path += (";" + $_.FullName) }
# フォーマットフォルダ配下のフォーマットファイルを読み込む
Get-ChildItem $FORMAT_HOME -recurse -include *.ps1xml | % { Update-FormatData -prependPath $_.FullName }