ラベル 言語仕様 の投稿を表示しています。 すべての投稿を表示
ラベル 言語仕様 の投稿を表示しています。 すべての投稿を表示

2010年3月4日木曜日

インポート後の関数の挙動

ScriptBlockを引数に持つような関数を、
モジュールマニフェストファイルを介してImport-Moduleで取り込むと、
ドットソースで読み込んだ場合と違はう動作をしてしまうようです。

モジュールマニフェストの作成方法については、こちらの投稿をどうぞ。

以下、検証サンプルコードです。

ソースコード:
# ファイル名:Test-ScriptBlock.ps1
function Test-ScriptBlock
{
[ScriptBlock]$block = $args[1]
Get-ChildItem -Path $args[0] | %{ '[{0}]:[{1}][{2}]' -f $_.Name, ( $_.Name -like "*a*" ), ( &$block ) }
}

PowerShellのコンソール:

PS> # Import-Moduleでファイルを直接取り込む。
PS> Import-Module .\Test-ScriptBlock.ps1 -Verbose
詳細: パス 'Test-ScriptBlock.ps1' からモジュールを読み込んでいます。
詳細: スクリプト ファイル 'Test-ScriptBlock.ps1' をドット ソース形式で読み込んでいます。

PS> Test-ScriptBlock $Env:PROGRAMFILES { $_.Name -like "*a*" }
結果出力( [アイテム名][True][True]または[アイテム名][False][False] )

PS> # Import-Moduleで、モジュールマニフェストを介してファイルを取り込む。
PS> # Test-ScriptBlock.ps1を含むモジュールマニフェストを作成(.\manifest.psd1)
PS> New-ModuleManifest
PS> Import-Module .\manifest.psd1 -Verbose
詳細: 関数 'Test-ScriptBlock' をインポートしています。
PS> Test-ScriptBlock $Env:PROGRAMFILES { $_.Name -like "*a*" }
結果出力( [アイテム名][True][False]になるものが存在する )
PS> # ↑引数を介して渡したスクリプトブロックの実行結果は常にFalse

PS> Test-ScriptBlock $Env:PROGRAMFILES { -not ( $_ ) }
結果出力( [アイテム名][-----][True])
PS> # ↑$_にオブジェクトが正しく渡されていないことを意味する


上記サンプルだけでは詳細は分かりませんが、
とりあえずは以下のことが言えそうです。
・ドットソースで取り込んだ関数の状態≠インポート後の関数の状態
・インポート後の関数は挙動がおかしい?

新しいことが分かったら関連のエントリーを作成することとします。

関連投稿:
PowerShellの「モジュール」について、その2

2010年2月28日日曜日

イベントにスクリプトブロックを指定する

PowerShellでもイベントを使えないかと思いネットで調べてみました。
できるだろうなぁ、と思っていましたが、できるようです。
こちらのサイトに、たいへん分かりやすくまとめられています。

結論のみ書かせていただきますと、以下のように書けばよいです。
[オブジェクト]. add_[イベント名]( スクリプトブロック )

何故イベントの事を調べていたかといえば、
ファイルのバックアップについて、
「更新されたファイルのみをバックアップできない??」
と知人より質問を受けたのがきっかけになります。
やり口としては、こちらのサイトに紹介されている、
FileSystemWatcherオブジェクトを使用する方法が良さそうなのですが、
ここでイベントを使う必要があるわけです。

勉強がてら、PowerShellで作ってみようとしてます。
勢いでファイルの更新情報を拾う部分だけ書いてみました。
動きはするようなので、サンプルコードとして晒してみます。

サンプルコード:

#FileWatchSample.ps1
param
( [string]$Directory = "D:\", [switch]$IncludeSubDirectory = $true )

#制御用フォーム準備
$label = New-Object -TypeName "System.Windows.Forms.Label"
$label.Text = "監視終了時は、このフォームを閉じてください。"
$label.Size = New-Object -TypeName "System.Drawing.Size" `
-ArgumentList @( $label.PreferredWidth, $label.PreferredHeight )

$form = New-Object -TypeName "System.Windows.Forms.Form"
$form.Text = "ファイル監視中"
$form.AutoSize = $true
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Fixed3D
[void]$form.Controls.Add( $label )

#FileSystemWatcher準備
$fileSystemWatcher = New-Object -TypeName "System.IO.FileSystemWatcher" `
-ArgumentList @( $Directory )
$fileSystemWatcher.IncludeSubdirectories = $IncludeSubDirectory
$fileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::CreationTime.value__ `
-bor [System.IO.NotifyFilters]::FileName.value__
$fileSystemWatcher.Filter = "*.*"

$fileSystemWatcher.add_Changed( `
{ Write-Host ( "{0} changed: {1}" -f [DateTime]::Now.ToString(), $_.FullPath ) } )
$fileSystemWatcher.add_Created( `
{ Write-Host ( "{0} created: {1}" -f [DateTime]::Now.ToString(), $_.FullPath ) } )
$fileSystemWatcher.add_Deleted( `
{ Write-Host ( "{0} deleted: {1}" -f [DateTime]::Now.ToString(), $_.FullPath ) } )
$fileSystemWatcher.add_Renamed( `
{ Write-Host ( "{0} renamed: {1} => {2}" `
-f [DateTime]::Now.ToString(), $_.OldFullPath, $_.FullPath ) } )
$fileSystemWatcher.SynchronizingObject = $form

#ファイル監視開始
$fileSystemWatcher.EnableRaisingEvents = $true
#終了時はフォームを閉じること。
$form.ShowDialog() | Out-Null



参照リンク
PowerShell Memo : VisualBaiscとPowerShellのイベント処理の比較
present : ファイルの作成・削除・変更をイベントで知る方法
MSDN : FileSystemWatch

参考コマンド

2010年2月24日水曜日

PowerShellからWin32APIを使う

つい最近の話ですが、
PowerShellからiniファイルのデータを拾いたいと思う事がありました。
Win32APIのGetPrivateProfileString関数を
使えれば早いのですがそんなことが可能なのか。。。

実は、.NET FrameworkのP/Invoke(Platform Invokeの略?)の
仕組み機能を使用することで、PowerShellからWin32APIを使用できます。
例によって、.NET FrameworkをPowerShellから呼び出します。

今回のは、.NETにそんなに詳しくない身としては少々ハードルが高く、
一から実装することは難しいですので、
今回はこちらのサイトからソースコードを拝借しました。
ありがとうございます!

単にコードを示すだけでは私自身の勉強にならないため、
MSDNのクラスライブラリ等を使い、なるべくコードの意味を調べるようにしました。
関連リンクは後ほど。。。

サンプルコード:

# PowerShellからのPlatform APIの呼び出しは、
# .NET Frameworkの提供するP/Invoke機能を介して実施することができる。
# Invoke-Win32では、P/Invokeを実行用に一時的な型を定義し、
# そこに実装したStaticなメソッドを介してPlatformAPIを実行する。

function Invoke-Win32()
{
param
( [string]$dllName, [Type]$returnType, [string]$methodName, [Object[]]$parameterInfos )

$parameterTypes = $parameterInfos | %{ $_[ 0 ] }
$parameters = $parameterInfos | %{ $_[ 1 ] }

# カレントのアプリケーションドメインに、
# P/Invokeを実行するメソッドを持つ独自の型を定義する。

$domain = [AppDomain]::CurrentDomain
$name = New-Object Reflection.AssemblyName 'PInvokeAssembly'
$assembly = $domain.DefineDynamicAssembly( $name, 'Run' )
$module = $assembly.DefineDynamicModule( 'PInvokeModule')
$type = $module.DefineType( 'PInvokeType', "Public,BeforeFieldInit" )

# P/Invokeを呼び出すためのパラメーターを準備する。
# PlatformAPIに渡すパラメーターを保持する配列

$inputParameters = @()
# PSReference型パラメーターの位置を保持する配列
$refParameters = @()

for( $counter = 0; $counter -lt $parameterTypes.Length; $counter++ )
{
# パラメーターの型が「PSReference」のものについては、関数から戻ってきた値を拾えるようにする必要がある。
if( $parameterTypes[ $counter ] -eq [Ref] )
{
# 呼び出しの際に[out]をつける必要があるため、そのパラメーターの位置を退避しておく。
# 配列の数より1つ大きな数を保持しておく必要がある。(理由は後述)

$refParameters += $counter

# PSReference型を、.NETオブジェクトの参照型に書き換える。
$parameterTypes[ $counter ] = $parameters[ $counter ].Value.GetType().MakeByRefType()

# 関数呼び出し時に使用するパラメーター一覧に値を追加する
$inputParameters += $parameters[ $counter ].Value
}
# そうでないものについては、関数呼び出し時に使用するパラメーター一覧にただ追加するのみ
else
{
$inputParameters += $parameters[ $counter ]
}
}

# アセンブリーの動的メソッドとして、PlatformAPIを定義する
$method = $type.DefineMethod( $methodName, `
'Public,HideBySig,Static,PinvokeImpl', `
$returnType, `
$parameterTypes )
# PSReference型のパラメーターは、out属性のパラメーターとする。
foreach( $refParameter in $refParameters )
{
# 0番目の要素は戻り値の情報になるため、配列の要素番号+1を指定する必要がある。
$method.DefineParameter( ( $refParameter + 1 ), "Out", $null )
}

# P/Invokeのコンストラクターを設定する
$ctor = [Runtime.InteropServices.DllImportAttribute].GetConstructor( [string] )
$attr = New-Object Reflection.Emit.CustomAttributeBuilder $ctor, $dllName
$method.SetCustomAttribute( $attr )

# 一時的な型を作成し、そのメソッドとしてPlatformAPIを実行する。
$realType = $type.CreateType()
$returnObject = $realType.InvokeMember( $methodName, `
'Public,Static,InvokeMethod', `
$null, `
$null, `
$inputParameters )

# PSReference型で受け取ったパラメーターの値を更新する。
foreach( $refParameter in $refParameters )
{
$parameterInfos[ $refParameter ][ 1 ].Value = $inputParameters[ $refParameter ]
}
# PlatformAPIの戻り値を返す
return $returnObject
}

# GetPrivateProfileStringを呼び出してみる。
$iniFilePath = 'C:\test.ini'
$returnValue = New-Object System.Text.StringBuilder 500
$parameterInfos = @( @( [string], [string]"FrontOtherService1" ), `
@( [string], [string]"Name" ), `
@( [string], [string]"" ), `
@( [System.Text.StringBuilder], [System.Text.StringBuilder]$returnValue ), `
@( [int], [int]$returnValue.Capacity ), `
@( [string], [string]$file ) )

$returnValue = Invoke-Win32 -dllName "kernel32.dll" `
-returnType ( [UInt32] ) `
-methodName "GetPrivateProfileString" `
-parameterInfos $parameterInfos
$returnValue.ToString()



他にもいろいろなWin32API関数が使えるはずです。
その際、.NET上で動くコード(マネージコード)と
Win32APIを動かすときのコード(ネイティブコード)との対応を
理解しておく必要がありますが、ツールで調べることもできます
P/Invoke Interop Assistant)。

で、今新たに知りたいと思っているのは、
PowerShellからDelegateを使用する方法です。
イベントを使ったり、マルチスレッドを実装する場合には欠かせません。
これについても、おいおい勉強していければいいかなぁ。。。

参考コマンド:


参考リンク:
Precision Computing : Get the Owner of a Process in PowerShell – P/Invoke and Ref/Out Parameters
MSDN : A Closer Look at Platform Invoke
P/Invoke.NET
CodePlex : P/Invoke Interop Assistant

関連投稿:
PowerShellから.NETのアセンブリを呼ぶ

修正履歴:

2010年2月14日日曜日

PowerShellにおける型オブジェクトの取得

以前のエントリー(このブログの最初のエントリーです)において、
関数を記述する際引数の型を縛る場合には、以下のような記述を行う必要がある、
ということを書きました。
例えば、パラメタを文字列型に固定したい場合には、
変数の前に[型名]という塊を添えました。

ここで、「[型名]」自体に何か意味はあるかと思い調べてみたところ、
こいつはSystem.Typeを継承したSystem.RunTimeという型のオブジェクトみたいです。
オブジェクトなので、そのメソッドなんかも利用できます。
また、この記述方法と-is演算子を用いれば、
オブジェクトの型チェックが簡単にできます。
Object -is [TypeName] というコードで型の判定ができるってのは、
コードが英語そのまんまですし、個人的にはかなりヒット(?)です。

検証スクリプト:

# 変数の型チェックに使用
$var_string = "defaultValue"
$var_string -is [string]
# 出力 → True


# [string]の型確認
Get-Member -InputObject ( [string] )
# 出力 → TypeName: System.RuntimeType(以下、メンバーなど)

# 他のクラスでやっても同様
Get-Member -InputObject ( [System.Runtime.InteropServices.DllImportAttribute] )
# 出力 → TypeName: System.RuntimeType(以下、メンバーなど)

# -is演算子の使用、クラスの継承関係は自動で考慮される
'sample_string' -is [string]
# 出力 → true
'sample_string' -is [object]
# 出力 → true
'sample_string' -is [uint32]
# 出力 → false

# 応用例:指定したディレクトリとそのサブディレクトリからファイルのみを取得
$TargetDirectory = 'D:\test'
$files = Get-ChildItem -Path $TargetDirectory -Recurse | ?{ $_ -is [IO.FileInfo] }$files | Get-Member
# 出力 → TypeName: System.IO.FileInfo(以下、メンバーなど)



PowerShellは触れば触るほど面白いなぁ、と思います。
シェルのセッション上のパイプを、.NET Frameworkのオブジェクトが動き回る、
という話についても、何か感覚的に分かってきた気がします。。。

参考サイト:

参考コマンド:

関連エントリー:
PowerShellのパラメータ属性に関するメモ

修正履歴:

2010年2月8日月曜日

PowerShellの変数のスコープ、その2

前回の続き。

PowerShellにおけるスコープとは何ぞや、という事ですが、
スクリプトブロックや関数の間で、
どの変数が見えて、どの変数が見えないか、
という事を表現するための概念のようです。

たとえば:
powershellのコンソール画面上で定義した変数は、
コンソールから呼び出した関数内でどのように認識されることになるかとか、
ある関数から別の関数を呼び出した場合に変数はどのように引き継がれるかとか、
関数内にスクリプトブロックを使用する場合に
スクリプトブロック内外で変数はどのように扱われるか、
といったような事を考える上で必要な知識です。

PowerShellの変数のスコープは、全部で4種類存在します。
詳細はいろいろな方がまとめていらっしゃいます。
その中で、ここでは++C++;// 未確認飛行 C(岩永様)
リンクを貼るにとどめます。

今回は、4種類の変数に加え、スコープを明示しない場合
(本エントリー内では「デフォルトスコープ」と記述)も加えた
合計の5種類のスコープの変数について調べてみました。

使用したスクリプト(スクリプトのスコープを確認する):
#変数を消しておく
Remove-Variable -Name "v" -ErrorAction SilentlyContinue
"0:[{0}][{1}][{2}][{3}][{4}]" -f "global", "script", "local", "private", "none"
"0:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
  &{
    "1:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
    &{ 
      #【xxxxxx:】の部分を、いろいろ変えながら遊んでみる。
      #選択肢 → なし、global:、script:、local:、private
      $xxxxxx:v = "a"
      "2:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
      &{ 
        "3:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
         #【yyyyyy:】の部分を、いろいろ変えながら遊んでみる。
        if( $v ) { $yyyyyy:v = "b" }
        "3:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
        &{
          "4:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
         #【zzzzzz:】の部分を、いろいろ変えながら遊んでみる。
         if( $v ) { $zzzzzz:v = "c" }
          "4:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
        }
        "3:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
      }
      "2:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
    }
    "1:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v
  }
"0:[{0}][{1}][{2}][{3}][{4}]" -f $global:v, $script:v, $local:v, $private:v, $v


このコードを、xxxxxx、yyyyyyおよびzzzzzzを変えながら実行した結果、
変数についていくつかの所見が得られたのでまとめておきます。
(あくまで私個人の所見であり、まだMSDN等での裏付けはとっていません)

---
①PowerShellの変数はスタックで実装されている?
 (PowerShellブログにこれを裏付ける記述がある
 子スクリプトブロックに入る時に新しい値がプッシュされ、
 ブロックを出るときにその値がポップされる。
 Get-VariableおよびSet-Variableの両コマンドレットを用いて、
 任意の場所のデータを取得/変更することが可能。
 スタックの起点にはグローバル変数の情報があり、
 スコープを明示することでアクセスできる?

②変数のスコープは、明示的に指定しない限りローカルスコープとなる。

③ローカルスコープで宣言した変数は、同一ブロック内ではプライベートスコープの変数としても参照できる。
 が、混乱を招く恐れがあるのでやらないようにしましょう。

④親ブロックのローカル変数の内容を子ブロックから参照する場合、デフォルトスコープで参照する必要がある。

⑤プライベートスコープの変数は、自身の定義されたブロック内でのみ有効となる。

⑥グローバルスコープの変数の書き換えを実施する場合には、スコープを明示する必要がある。

⑦powershellコンソール上で宣言された変数は、プライベートスコープで宣言しない限りグローバルスコープとなる。

⑧グローバルスコープとスクリプトスコープの両変数の挙動は変わらない?
 まさかねぇ…今は分からないですが、分かったらエントリー書きます。
---

①を知っておけば、その応用で他項目も類推可能かなぁ、と。。。
それと、変数はデフォルトスコープで使っていくのが良さそうです。

それにしてもすごいです。いろいろできそうですね、これ。
しかし、チームで実装する場合など、どこまで機能を使ってもいいかなど、
コーディング規約等で縛らないと収拾が付かなくなる気がします。

参考サイト:
PowerShell Blog : Controlling the Scope of Variables
++C++;// 未確認飛行 C : 変数

参考コマンド:
Get-Help about_Scopes
Get-Help Set-Variable -Full

修正履歴:

---
以下、上記「言えそうなこと」の根拠となる実行結果データの一部になります。
ここでは、yyyyyy:およびzzzzzz:はデフォルト(何も指定しない)とした場合の
例のみの掲載とします。
デフォルト
0:[global][script][local][private][none]
0:[][][][][]
1:[][][][][]
2:[][][a][a][a]
3:[][][][][a]
3:[][][b][b][b]
4:[][][][][b]
4:[][][c][c][c]
3:[][][b][b][b]
2:[][][a][a][a]
1:[][][][][]
0:[][][][][]

local:
0:[global][script][local][private][none]
0:[][][][][]
1:[][][][][]
2:[][][a][a][a]
3:[][][][][a]
3:[][][b][b][b]
4:[][][][][b]
4:[][][c][c][c]
3:[][][b][b][b]
2:[][][a][a][a]
1:[][][][][]
0:[][][][][]

private:
0:[global][script][local][private][none]
0:[][][][][]
1:[][][][][]
2:[][][a][a][a]
3:[][][][][]
3:[][][][][]
4:[][][][][]
4:[][][][][]
3:[][][][][]
2:[][][a][a][a]
1:[][][][][]
0:[][][][][]

script:
0:[global][script][local][private][none]
0:[][][][][]
1:[][][][][]
2:[a][a][][][a]
3:[a][a][][][a]
3:[a][a][b][b][b]
4:[a][a][][][b]
4:[a][a][c][c][c]
3:[a][a][b][b][b]
2:[a][a][][][a]
1:[a][a][][][a]
0:[a][a][a][a][a]

global:
0:[global][script][local][private][none]
0:[][][][][]
1:[][][][][]
2:[a][a][][][a]
3:[a][a][][][a]
3:[a][a][b][b][b]
4:[a][a][][][b]
4:[a][a][c][c][c]
3:[a][a][b][b][b]
2:[a][a][][][a]
1:[a][a][][][a]
0:[a][a][a][a][a]

2010年2月7日日曜日

PowerShellの変数のスコープ、その1

これまでの言語と違うようなので、
回数を多めにとってまとめておきます。

変数のスコープというと、C言語とかJavaだと、
for文とかif文の「{ }」内外で変数のスコープが変わるので云々、
ということでコード作成時に注意する必要がありました。
C言語の例は、こちらのサイトをご覧ください。

PowerShellの場合は、上記のような通常の制御構文の内外で、
変数のスコープが変わることはありません。
したがって、たとえばforループ内で宣言した変数を、
ループの外から普通に使用できたりもします。

作成例(制御構文とローカル変数の関係確認スクリプト):
#変数を消しておく
Remove-Variable  -Name a, b
[string]$a = "A"
if ( $true ) {
    $a = "a"
    $b = "b"
}
Write-Host ( '$a[{0}], $b[{1}]' -f $a, $b ) 
#出力 → $a[a], $b[b_inside_if]

for ( $counter = 0; $counter -lt 10; $counter++ ) {
    $a += $counter.ToString() 
    $c += $counter.ToString()
}
Write-Host ( '$a[{0}], $c:{1}' -f $a, $c )
#出力 → $a[a0123456789], $c[0123456789]


では、PowerShellにおけるスコープとは何ぞや、という事ですが、
実はスクリプトブロックや関数の間で、
どの変数が見えて、どの変数が見えないか、
という事を表現するための概念のようですが…
これについては、次回以降のエントリーで書きます。

参考サイト:
P's Members : スコープってなに?

参考コマンド:
Get-Help about_Scope

修正履歴:

2010年1月31日日曜日

PowerShellの「モジュール」について、その4

PowerShellブログの2010/1/23付けのエントリーに、
モジュールを作る際の注意事項が出ています。
覚えておいたほうがよさそうな話なので、
ざっくりとしたところだけここに残しておきます。

今回は結論から書きます。
マニフェストファイルを作成する際、
PowerShellHostVersionの指定するかどうかは慎重に決める必要があります。
さらに、仮に指定する場合には、
PowerShellHostNameをあわせて指定したほうがよさそうです。

PowerShellHostVersion指定、PowerShellHostName未指定の場合には、
どんなPowerShellホストから実行した場合でも、
PowerShellHostVersionに書かれた制限が有効になったかどうか
分からなくなっちゃうような動きをする事があるためです。

PowerShellブログに載っている例ですと、
Import-Moduleはエラーなしで実行できるが、
その中のコマンドレットや関数を実行しようとしたときに、
よく分からないエラーが出ています。
エラーが出ても、原因が追えないとさすがにまずいです。
いわんやモジュールはどんな環境で使用されるか分からないですし、
問題を追うのが難しくなってしまうのはちょっと…
ということになります。

ということで、当面のとりあえずのやり口は以下の感じですかね:
①特別な理由がない限り、PowerShellHostVersionは設定しない。
②PowerShellHostVersion指定時にはPowerShellHostNameも指定する。
③バージョンが異なる複数のモジュールをサポートする場合は…
 Descriptionに書くなどしてユーザに周知する。

上記エントリを見てる感じ、ここには修正が入る気がします。
モジュールでPowerShellのHost情報の制限を考えている場合には、
PowerShellブログを見つつもう少し様子を見たほうがいいかも。。

参考サイト:
Windows PowerShell Blog : PowerShellHostVersion - WTF?
Shay Levy : Module manifest gotcha

修正履歴:

2010年1月24日日曜日

パイプラインから取得するオブジェクトの扱い

PowerShellで関数を実装する際には、
パイプからの入力を許可することでぐっとPowerShellっぽくなります。

関数の引数をパイプラインから取得できるようにするためには、
引数の定義のところで設定をしておけばOKでした。
参考:PowerShellのパラメータ属性に関するメモ

実装する場合には、大雑把に以下の実装を行うこととなります。
①共通事前処理 → Beginブロック内
②オブジェクト個別の処理 → Processブロック内
③共通事後処理 → Endブロック内

ちなみに、パイプから流れてきたオブジェクトを
コレクションのまま処理したい場合、実装の流れは以下のようになります。
①でオブジェクトを受け入れるための配列を用意し、
②で用意した配列に順に値を詰めていき、
③に実施したい処理を書く。

実装例:

# Test-Pipeline.ps1
function Test-Pipeline
{
Param #パラメータ
( [int][Parameter(Mandatory = $True ,ValueFromPipeLineByPropertyName=$True)]$Length)
begin #共通事前処理
{ [int]$share = 0; "begin:" + $share.ToString() | Out-Host }
Process #オブジェクト個別の処理
{
[int]$local = 0; $local += $Length; $share += $Length;
$local.ToString() + "," + $share.ToString() | Out-Host
}
end #共通事後処理
{ "end:" + $share.ToString() | Out-Host }
}

"a",1,"bb","ccc" | Test-Pipeline

↓↓↓↓上記スクリプトの出力(若干歪んでいます)↓↓↓↓
begin:0
1,1
Test-Pipeline : 入力オブジェクトをコマンドのパラメーターにバインドできません。コマンドがパイプライン入力を受け入れないか、または入力とそのプロパティが、パイプライン入力を受け入
れるいずれのパラメーターにも一致しません。
発生場所 D:\Program Codes\MyPowerShell\test-pipe\test-pipe.ps1:16 文字:33
+ "a",1,"bb","ccc" | Test-Pipeline <<<< + CategoryInfo : InvalidArgument: (1:Int32) [Test-Pipeline]、ParameterBindingExcepion + FullyQualifiedErrorId : InputObjectNotBound,Test-Pipeline


2,3
3,6
end:6


参考コマンド:

Get-Help about_pipelines


修正履歴:
2010/1/28 コメントを緑色にしてみた。

2010年1月4日月曜日

PowerShellのヘルプメッセージ

自前のプログラムを作成・公開する場合には、
使い方の情報を「ヘルプメッセージ」として開示することが多いです。

↑この手の文は一般的に「ヘルプメッセージ」と呼ぶものと思ってましたが、
どうやらPowerShellではヘルプトピックというらしい。
検索で作り方が出なかったのは、キーワード「ヘルプメッセージ」が悪かったようで。。。

また、PowerShellでは、上記ヘルプメッセージも
MamlCommandHelpInfoオブジェクトとして定義されています。
その恩恵でクラスからほしい情報のみを取り出すことも容易であり、
独自ヘルプトピックの使用にも活用できます。

以下、スクリプトのコメントベースのヘルプで記述できる項目です。
キーワード名が「-」になっている項目は、自動で生成されるコンテンツです。
キーワード名 ざっくりとした説明指定
可能
回数
ヘルプトピック
での表題
MamlCommandHelpInfo
クラスのプロパティ名
関数/スクリプト名。名前Name
.SYNOPSIS 関数/スクリプトの簡単な説明。1概要Synopsis
関数/スクリプトのI/F。構文syntax
.DESCRIPTION 関数/スクリプトの詳細な説明。1説明description
.PARAMETER パラメータ名 パラメータの説明。Nパラメーターparameters
パラメータの属性一覧。パラメータのところに出力される。
.INPUTS関数/スクリプトにパイプ可能なオブジェクト型の説明。1入力inputTypes
.OUTPUTS関数/スクリプトから返されるオブジェクト型の説明。1出力returnValues
.NOTES関数/スクリプトに関する追加情報。1メモ
.EXAMPLE関数/スクリプトの使用例。各例に本キーワードを指定。N1examples
.LINK関連トピックの情報。N関連するリンクrelatedLinks
注釈情報(get-helpコマンドの使い方)。注釈

コマンドベースのヘルプを記述できるのは以下の3箇所のいずれかになります。
①関数本体の先頭
②関数本体の末尾
③Functionキーワードの直前(複数行の空白行をおいてはならない)

作成例(ヘルプを①に記述した場合):

# sample4.ps1
Function Show-SampleTopic
{
<# .SYNOPSIS SYNOPSISには概要を書く。 .DESCRIPTION DESCRIPTIONには詳細説明を書く。 改行してみた。 .PARAMETER Param1 1つ目のパラメータ。 .PARAMETER Param2 2つ目のパラメータ。 改行してみた。規定値は"txt"。 .INPUTS なし。オブジェクトを Show-TopicSample にパイプすることはできません。 .OUTPUTS System.String。Show-TopicSample は、何か文字列を返します。 .EXAMPLE C:\PS> st -Param1 "File"
File

.EXAMPLE
C:\PS> st -Param1 "File" -Param2 "doc"

.EXAMPLE
C:\PS> st "File" "doc"

説明文その3
------------
長いので改行してみた。

説明が複数行に
わたっても
問題ありません。

.LINK
オンライン バージョン: http://www.sample.com/st.html

.LINK
Set-SampleTopic

.NOTES
追加情報いろいろ。
改行してみた。
#>

Param
([string]$Param1,[string]$Param2 = "txt")
Process
{ $Param1 + $Param2 | Out-Host}
}
Get-Help Show-SampleTopic -Full

↓↓↓↓上記スクリプトの出力(若干歪んでいます)↓↓↓↓

名前
Show-SampleTopic

概要
SYNOPSISには概要を書く。

構文
Show-SampleTopic [[-Param1] ] [[-Param2] ] []


説明
DESCRIPTIONには詳細説明を書く。
改行してみた。


パラメーター
-Param1
1つ目のパラメータ。

必須 false
位置 1
既定値
パイプライン入力を許可する false
ワイルドカード文字を許可する

-Param2
2つ目のパラメータ。
改行してみた。規定値は"txt"。

必須 false
位置 2
既定値
パイプライン入力を許可する false
ワイルドカード文字を許可する


このコマンドレットは、次の共通パラメーターをサポートします: Verbose、
Debug、ErrorAction、ErrorVariable、WarningAction、WarningVariable、
OutBuffer、および OutVariable。詳細については、
「get-help about_commonparameters」と入力してヘルプを参照してください。

入力
なし。オブジェクトを Show-TopicSample にパイプすることはできません。


出力
System.String。Show-TopicSample は、何か文字列を返します。


メモ
追加情報いろいろ。
改行してみた。

-------------------------- 例 1 --------------------------

C:\PS>st -Param1 "File"

File


-------------------------- 例 2 --------------------------
C:\PS>st -Param1 "File" -Param2 "doc"


-------------------------- 例 3 --------------------------
C:\PS>st "File" "doc"

説明文その3
------------
長いので改行してみた。

説明が複数行に
わたっても
問題ありません。


関連するリンク
オンライン バージョン: http://www.sample.com/st.html
Show-SampleTopic


参考コマンド:

Get-Help about_Comment_Based_Help


修正履歴:
2010/1/23 サンプルコードの表示のゆがみを修正
2010/1/30 MamlCommandHelpInfoオブジェクトに関する情報を追記

PowerShellのパラメータ属性に関するメモ、その2

PowerShellでは、入力パラメータの検証を自動化することができます。

VBScriptで(他の言語でも)プログラムを作成する場合、
場合によっては引数チェックだけでもかなりのコード量になっていました。
それに対して、Windows PowerShellでは、Paramセクション内に
検証用属性を書くだけで済みます。すごいっすねぇ、PowerShell。

コードも減り、その結果テストも減る、ということになるとは思いますが、
ブラックボックステストは厚めに実施する必要がある点は忘れぬよう。

以下、検証属性のいくつかを示します。
検証属性名 ざっくりとした説明
AllowNull() 必須コマンドレットパラメータへのNull値の設定を許容。
AllowEmptyString() 必須コマンドレットパラメータへの空文字の設定を許容。
ValidateScript(スクリプトブロック) パラメータ引数検証用のスクリプトを指定。結果がTrueの場合のみ有効とする。
ValidateSet(とり得る値の配列) パラメータ引数の有効値のセットを指定。
ValidateLength(n,m) パラメータ引数の最小の長さ(n)と最大の長さ(m)を設定。
※string型またはstring[]型にのみ使用可能。
ValidateRange(n,m) パラメータ引数の最小値(n)と最大値(m)を設定。
※文字列でも浮動小数点数にも使用可能。文字列の場合は、パラメータが整数として評価される。浮動小数点数の場合は、パラメータを四捨五入した数として評価される。
ValidatePattern(パターン文字列) パラメータ引数の最小値(n)と最大値(m)を設定。
※汎用的だが人によっては読めないかも?

パラメータ検証に失敗した場合には、エラーが発生してスクリプトが止まります。

実行例:
# sample3.ps1
Function Test-Function
{
Param
(
[string[]]
[Parameter(Mandatory=$true)]
[ValidateLength(1,10)]
$Test
)

Process
{ $Test.GetType() | Out-Host }
}

Test-Function -Test "12345678901"

#出力↓
test-function : パラメーター 'Test の引数を確認できません。
引数の長さ 11 が長すぎます。引数の長さを "10" 以下にして、
コマンドを再度実行してください。
(以下略)


参考コマンド:
Get-Help About_Functions_Advanced_Parameters

2009年12月22日火曜日

PowerShellの「モジュール」について、その3

今回も、MSDNの記事の大雑把なまとめ&和訳記事です。
内容がかなり怪しいので、MSDNの本文を確認する必要ありです
(ブログの本文も、理解できたところから随時修正します)。

---
前回の続きです。
New-ModuleManifestで生成したpsd1ファイルには、
コマンドレットで指定しなかった項目がたくさんできています。
これら拡張項目は、psd1ファイルを直接編集して指定することになります。
以下、キー名と指定する内容を列挙します。

キーワード名 ざっくりとした説明
Versionモジュールのバージョン。デフォルトは「1.0」。
GUIDモジュールを一意に識別するためのID。
デフォルトは、マニフェストが作成された際にPowerShellにより作成されるGUID。
PowerShellVersionこのモジュールが動作するPowerShellの、最小バージョンの番号を指定。
PowerShellHostNameモジュールが動作するPowerShellホストを指定。
PowerShellHostVersionモジュールが動作するPowerShellホストの、最小バージョンの番号を指定。
DotNetFrameworkVersionモジュールが動作する.NET Frameworkの、最小バージョンの番号を指定。
CLRVersionモジュールが動作するCLRの、最小バージョンの番号を指定。
ProcessorArchitectureモジュールが動作するプロセッサアーキテクチャを指定。
ScriptsToProcessモジュールのインポート時にcaller's session stateで実行される
スクリプトファイル(.ps1)を列挙。
※モジュール実行環境の構築に使用できそうですね。
FunctionsToExportモジュールがcaller's session stateにエクスポートする関数を列挙。ワイルドカードによる指定も可能。
既定では、全関数がエクスポートされる。
CmdletsToExportモジュールがcaller's session stateにエクスポートするコマンドレットを列挙。ワイルドカードによる指定も可能です。
既定では、全コマンドレットがエクスポートされる。
VariablesToExportモジュールがcaller's session stateにエクスポートする変数を列挙。ワイルドカードによる指定も可能。
既定では、全変数がエクスポートされる。
AliasesToExportモジュールがcaller's session stateにエクスポートするエイリアスを列挙。ワイルドカードによる指定も可能。
既定では、全エイリアスがエクスポートされる。
ModuleList該モジュールにパッケージされた全モジュールをcsv形式で列挙。
PrivateDataModuleToProcessで指定したルートモジュールに渡す必要のあるprivateデータを指定。
RequiredModulesモジュールのインポートに先立ちインポートされている必要のある
モジュールを列挙。

編集完了後には、忘れずにファイルを保存しましょう。

参考サイト
How to Write a Module Manifest
Writing a Windows PowerShell Module

PowerShellの「モジュール」について、その2

今回も、MSDNの記事の大雑把なまとめ&和訳記事です。
内容がかなり怪しいので、MSDNの本文を確認する必要ありです
(ブログの本文も、理解できたところから随時修正します)。

---
先日の記事の続きです。モジュールの作り方には2通りあります。
モジュールマニフェストファイル(拡張子psd1のファイル)を直接作るか、
New-ModuleManifestコマンドレットで直接指定するか、のどちらかです。

New-ModuleManifestでpsd1ファイルを作成する場合、
画面の出力に合わせて、いくつかの項目を入力することになります。
これらはpsd1最終的にはpsd1ファイルに反映されます。

以下、このコマンドレットで入力する情報とその内容を列挙します。
よくわからない項目はタイトルを青文字の斜体で書いておき、
わかり次第追記していくこととします。

キーワード名 ざっくりとした説明
Path新しく作成するモジュールマニフェストファイルのパスを指定。
ファイルの拡張子は「psd1」とする必要がある。
NestedModules[]入れ子モジュールとしてインポートするスクリプトモジュールファイル(.psm1)やアセンブリ(.dll)を列挙する。
入れ子モジュールのメンバは、明示的にエクスポートを実施しない限り、ルートモジュール(ModuleToProcessにて指定)からのみ参照可能となる。
Author製作者名。
CompanyName会社名。
Copyrightモジュールのコピーライト情報。
ModuleToProcessルートモジュールとして、psm1ファイル又はDLLを1つ指定する。
ここで指定したファイルは、GlobalSessionStateにインポートされることになる。省略した場合には、そのマニフェスト自身がルートモジュールとみなされる。
Descriptionモジュールマニフェストの簡単な説明を記述する。
TypesToProcess[]モジュールのインポート時に読み込まれる型ファイル(type files)を列挙する。PowerShellは、タイプファイルを元に.NET Frameworkのタイプにメンバを追加する。
省略時は、空リストとみなされる。
FormatsToProcess[]モジュールのインポート時に読み込まれる書式ファイル(formatting files)を列挙する。
省略時は、空リストとみなされる。
RequiredAssemblies[]モジュールで使用するアセンブリファイルを列挙する。
省略時は、空リストとみなされる。
FileList[]モジュールに含まれるすべてのファイルを指定する。
省略時は、空リストとみなされる。

これらが終わると、Pathで指定した先にpsd1ファイルが作成され、
以後、Import-Moduleでモジュールを使用できるようになります。
ちなみに、すべてのセッションにインポートしたい場合は、
プロファイルにImport-Moduleコマンドレットを追加する必要があります。

作成例:
New-ModuleManifestコマンドで作成する例です。

PS D:\> New-ModuleManifest

コマンド パイプライン位置 1 のコマンドレット New-ModuleManifest
次のパラメーターに値を指定してください:
Path: D:\MyModule.psd1
NestedModules[0]:
Author: 作者名
CompanyName: 作者名
Copyright: コピーライト
ModuleToProcess:
Description: 説明は簡潔に書きましょう。
TypesToProcess[0]:
FormatsToProcess[0]:
RequiredAssemblies[0]:
FileList[0]:
PS D:\>


FileListの後ろにエラーが出なければ、
最初にPathで指定したpsd1ファイルができているはずです。
今回の場合、↓のようなファイルが出力されました。

# MyModule.psd1
# 以下、見やすさを考えて若干レイアウトに手を加えています。
# モジュール 'MyModule' のモジュール マニフェスト
# 生成者: 作者名
# 生成日: 2009/12/22
@{
# ModuleToProcess に指定されているモジュールの入れ子になったモジュールとしてインポートするモジュール
NestedModules = @()
# このモジュールの作成者
Author = '作者名'
# このモジュールの会社またはベンダー
CompanyName = '作者名'
# このモジュールの著作権情報
Copyright = 'コピーライト'
# このマニフェストに関連付けられているスクリプト モジュール ファイルまたはバイナリ モジュール ファイル
ModuleToProcess = ''
# このモジュールの機能の説明
Description = '説明は簡潔に書きましょう。'
# このモジュールをインポートするときに読み込まれる型ファイル (.ps1xml)
TypesToProcess = @()
# このモジュールをインポートするときに読み込まれる書式ファイル (.ps1xml)
FormatsToProcess = @()
# このモジュールをインポートする前に読み込まれている必要があるアセンブリ
RequiredAssemblies = @()
# このモジュールに同梱されているすべてのファイルのリスト
FileList = @()
# このモジュールのバージョン番号です。
ModuleVersion = '1.0'
# このモジュールを一意に識別するために使用される ID
GUID = '3b6c99fd-98f2-47c4-84d5-aa953a7ce6b3'
# このモジュールに必要な Windows PowerShell エンジンの最小バージョン
PowerShellVersion = ''
# このモジュールに必要な Windows PowerShell ホストの名前
PowerShellHostName = ''
# このモジュールに必要な Windows PowerShell ホストの最小バージョン
PowerShellHostVersion = ''
# このモジュールに必要な .NET Framework の最小バージョン
DotNetFrameworkVersion = ''
# このモジュールに必要な共通言語ランタイム (CLR) の最小バージョン
CLRVersion = ''
# このモジュールに必要なプロセッサ アーキテクチャ (なし、X86、Amd64、IA64)
ProcessorArchitecture = ''
# このモジュールをインポートする前に呼び出し元の環境で実行されるスクリプト ファイル (.ps1)
ScriptsToProcess = @()
# このモジュールからエクスポートする関数
FunctionsToExport = '*'
# このモジュールからエクスポートするコマンドレット
CmdletsToExport = '*'
# このモジュールからエクスポートする変数
VariablesToExport = '*'
# このモジュールからエクスポートするエイリアス
AliasesToExport = '*'
# このモジュールに同梱されているすべてのモジュールのリスト
ModuleList = @()
# ModuleToProcess に指定されているモジュールに渡すプライベート データ
PrivateData = ''
# このモジュールをインポートする前にグローバル環境にインポートされている必要があるモジュール
RequiredModules = @()
}


New-ModuleManifestでは指定していないものもたくさん出てきています。
自動生成されたコメントで何を示すものかは大体検討がつくと思いますが、
次の投稿で簡単に説明を書いてみることにします。

参考サイト
How to Write a Module Manifest
Writing a Windows PowerShell Module

PowerShellの「モジュール」について

今回は、MSDNの記事の大雑把なまとめ&和訳記事です。
内容がかなり怪しいので、MSDNの本文を確認する必要ありです
(ブログの本文も、理解できたところから随時修正します)。

---
PowerShellには「モジュール」という仕組みがあります。
PerlやPythonにある「モジュール」と似たようなものになります。
用途例としては、以下があげられます。

①ライブラリとして(Library)
→ 共通処理をまとめたパッケージとして使用可能。
  さまざまなクラスから共有される.NETクラスのようなイメージ。

②環境設定にて(configulation)
→ コマンドレットを呼び出した際の環境設定に使用可能。
  ドットソースやProfileファイル($Profile)を使うのと同じ感じ。

③アプリケーションにて(Application)
→ 既存の機能を再パッケージしたりカスタマイズするときに使用可能。
  あるパッケージを、特定の状況下でのみで適用・使用、というも可能。

④要コンパイルのコード開発とコンパイル済みモジュールの配布にて
 (Compiled Code Development and Distribution)
→ SnapInを作成せずとも作成したコンパイル済みモジュールを使用可能。


共通関数を作ったり実行環境をカスタマイズするのに使える…便利そうです。
PowerShellである程度の規模のものを作っていけば、
自ずとその恩恵を受けることになるのでしょう、たぶん。


では、モジュールは一体どう作ればいいかという話なのですが…
それはまた次の投稿で書くこととします。

参考サイト
How to Write a Module Manifest

2009年12月19日土曜日

PowerShellのパラメータ属性に関するメモ

関数やスクリプトに引数を指定させる場合、
Paramブロックの中に引数名等の情報を記述できる他、
各パラメータに対して入力規則や規定値なんかも設定できます。

属性名ざっくりとした説明
[.NET Frameworkクラス名]$変数名 変数の型を指定。デフォルトでは変数には任意の型を代入可。
$変数 = デフォルト値を指定。
Position = 整数 パラメータの設定する位置を指定。
Mandatory = $True 必須指定オプションの場合に$Trueを指定。
ValueFromPipeline = $True パイプラインから流れてきたオブジェクトそのものを使用できるようにする場合に指定。
ValueFromPipelineByPropertyName = $True パイプラインから流れてきたオブジェクトの持つプロパティとパラメータをバインドする場合に指定。
ParameterSetName = パラメータセット名 パラメータセット名を指定。省略時は、全パラメータセットに含まれると見なされる。
HelpMessage = 説明メッセージ パラメータの説明メッセージを指定。

実装例:
sample1.ps1とsample2.ps1からは、同一の出力が得られます。
# sample1.ps1
Function Test-Sample01
{
Param
(
# パイプラインより受けとったオブジェクトの、Nameプロパティの内容を受け取る。
[Parameter(Position=0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)]$Name,
[Parameter(Position=1, Mandatory = $True)][string]$MyString,
# スイッチパラメータの指定
[Parameter(Position=2)][switch]$MySwitch
)
Process
{ $Name + $MyString +$MySwitch.ToString() | Out-Host }
}
Get-Process | Test-Sample01 -MyString "Test" -MySwitch

# sample2.ps1
Function Test-Sample02
{
Param
(
# パイプラインより受け取ったオブジェクトをそのまま使用。
[Parameter(Position=0, Mandatory = $True, ValueFromPipeline = $True)]$Object,
# 変数MyStringのデフォルト値は"Test"
[Parameter(Position=1, HelpMessage="My string...")][string]$MyString = "Test",
[Parameter(Position=2)][switch]$MySwitch
)
Process
{ $Object.Name + $MyString + $MySwitch.ToString()| Out-Host}
}
Get-Process | Test-Sample02 -MySwitch



参考サイト:
スクリプトセンター(TechNet)
IIS 7.0 向けの PowerShell コマンドレットの作成(TechNet)
Hiro's .NET Blog
パラメータセットの説明

参考コマンド:

Get-Help About_Functions_Advanced_Parameters