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月26日金曜日

PowerShellのインストール

これまで使い方等の細かい話ばっかり書いていましたが、
どこからダウンロードできるかという話は書いていなかったので、
なんとなく書こうと思います。
一応、書くネタがないだけですわけではありません。

Windows PowerShellの最新のバージョンは2です。
そのインストーラは、以下より入手できます。
http://connect.microsoft.com/windowsmanagement/Downloads
お使いのOSおよびマシンのプロセッサアーキテクチャに応じて、
適切なものをダウンロードし、インストールすればOKです。
開発環境も付いてきますので、すぐに開発ができます。
あんなものやこんなものを作って楽しみましょう♪

===
今回本当に書きたかった話はここからです。

上記リンクから取得したインストーラを使っても、
インストールに失敗してしまう事が時々あります。
私自身、仕事用マシン(WindowsXP SP3のx86)でこの問題に悩みました。
あのエラーメッセージで途方に暮れた型は私以外にもいらっしゃるはず。

その後の調査により、いつの間にかPowerShell v2がインストールされており、
重複でインストールしようとしてエラーが出ていたようでした。
今となっては、WindowsUpdateに混ざって勝手にインストールされていた
というのが真相だろうなぁと思っています。ちなみに、KB968930です。

会社では、いつの間にかPowerShellが使えるようになっていた、
という話をしてくれた人が何人かいました。
インストール時に問題が起こるとしたら、
まずはこちらの原因を疑ってみるとよろしいかと思います
(特に、WSUSを使われている皆様)。
インストールに失敗したあなたのマシンの「アクセサリ」をのぞくと、
その中にもうWindows PowerShell ISEがあるかも…。。


上記以外でインストールに失敗する原因としては、
PowerShellのv1をアンインストールしていなかったとか、
ダウンロードしたインストーラが間違っていたとか、
といった理由も考えられます。
PowerShellBlogにもエントリーが出ておりますので、
詳細はそちらをご覧ください。

2010年2月24日水曜日

PowerShellからCOMのタイプライブラリを使う

一つ前の投稿で紹介したCodePlexのサイトに、
COMのタイプライブラリを使用するためのツールが公開されています。
CodePlex : Type Library Importer in Managed Code
この機能、前からずっと欲しいと思ってました。
私以外にも欲しいと思っていた人もいらっしゃると思いますし、
喜び半分でこのサイトでもリンクのみ公開しました。

WSHでは、タイプライブラリを容易に利用することができました
VBScriptはActiveX経由で簡単にCOMオブジェクトが作成できる上、
タイプライブラリも簡単に使うことができました。
「VBScriptはCOMのネイティブ言語」という旨の話が書かれていた
サイトがありましたが、そのとおりだと私も思います。

PowerShellでも、
New-Objectコマンドレットを用いてCOMオブジェクトを取得できます。
しかし、WSHと違いタイプライブラリを読み込む方法がなかったため、
プロパティに設定されている数字が何を表すかを調べるのが、
若干面倒な作業だったりします。
しかし、Type Library Importer in Managed Codeが使用できれば、
その苦労ともおさらばできることでしょう(喜)。

参照リンク
CodePlex : Type Library Importer in Managed Code

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年2月1日月曜日

Windows Server 2008 R2から解禁されるPowerShellモジュール

technetをぷらっと見ていたら偶然見つけました。

Windows PowerShell の役割と機能のコマンドレットの新機能

Active Directoryとかグループポリシーオブジェクトとか
フェールオーバークラスタリングとかOSのバックアップとか、
便利そうなコマンドレットが盛りだくさんです。
環境構築で使いたいものも、日常の運用で使いたいものもあります。
Windows Server 2008 R2の仕事が入れば、
いろいろ遊べる実機が手に入る実際に使用できるのになぁ。

2008 R2でパワーアップしたActive Directoryでは、
PowerShellからのみ使用可能な機能がいろいろとあるようです。
詳細は、MS安納様がTechED2009にて解説されていましたが、
ご自身のブログでも関連情報を数多く公開していらっしゃいます。

そういえば、環境構築の仕事において、
グループポリシーの設定で悩んでいる同僚がいました。
ひとつひとつ手で設定していた、わけではないと思いますが、
VBScript等を用いた自動化に至らなかったのでしょう、たぶん。
グループポリシーの設定の自動化はPowerShellから可能になった、
ということであれば、
ここをしっかり目に勉強しておくと後々いいことがあるかも。。。