2010年6月15日火曜日

CA XOsoft PowerShell

業務の関係上、しばらくPowerShellから遠ざかっていましたが、
またしばらくPowerShellと戯れることが出来る見通しとなりました。
ということで、数カ月ぶりに記事を書きます。

ここしばらく、JP1やらARCserveやらを使った運用をいろいろとやっていました。
そんなこともあり、何気なくARCserveの情報を調べてみたところ、
何かすごくwktkさせてくれるものが出てきました♪

コントロールサービスって何なのさ(それ、レプリケーションでよろしく)

↑日本CAの中の皆様によるブログですが、
ARCserveのPowerShellスナップインが紹介されています。
CA XOsoftPowerShell?と言うそうです。
ネットを探すとマニュアルも出てきました。

…ちょうどARCserveが入った環境もあることだし、
仕事の合間にちょっと遊んでみたいなぁと思います。
何か所見が得られたら、このブログでフィードバックする感じで。

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月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から可能になった、
ということであれば、
ここをしっかり目に勉強しておくと後々いいことがあるかも。。。

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月30日土曜日

PowerShellからWMIを使う

PowerShellの登場により、
WMIの活用への敷居が一気に低くなったように思います。

technetによれば、WMI(Windows Management Instrumentation) とは:

WMIとは、WBEM(Web-Based Enterprise Management Initiative) を Microsoft が実装したものです。WBEM とは、エンタープライズのネットワークの管理情報にアクセスしたり、管理情報を共有したりするための標準を確立する業界イニシアティブの 1 つです。WMI は、管理環境にあるオブジェクトを説明するデータ モデルであるCommon Information Model (CIM) を統合的にサポートします。

要は、Windowsの各種管理に使用できる手段のひとつ、ってことです。
私も、PowerShellを書き始めるまでWMIは名前こそ知っていましたが、
何ができるのかとか、どういう風に使えばいいのかとか、
ほとんどまったく知らなかったってのが実際のところです。

しかしながら、PowerShellのテクノロジーを使用できる今、
Get-WmiObjectに-Listオプションをつけて実行することにより、
やりたいことを実現させてくれるWMIクラスを探すのも容易であり、
Get-Memberコマンドレットを使ってメンバーも簡単に確認にできます。
もちろん、WSHでもWMIを使用することができたのですが、
言語として検索機能をサポートしている点において、
PowerShellは非常に優れている、といって間違いないかと思います。
これは、PowerShellの登場によりWMIが一気に身近なものになった、
ということに他ならないのではないでしょうか。

なお、クラスを自分で探すのが面倒な場合とか、
デフォルトの名前空間以外のWMIクラスを使用する場合については、
Windows PowerShell Scriptomaticにより、
PowerShellのコードを自動生成し、それを必要に応じて加工したものが使えます。
どんなメソッドが使えるかは、Get-Memberで確認しましょう。

ということで、PowerShellを用いてシステムの運用管理を考える場合には、
PowerShellからWMIを積極的に使っていくことを検討してみてはどうでしょうか。

実装例:
PowerShellからWMIを使用し、残り容量が10%を切ったディスクのドライブレターを取得する。

#ディスク関連のWMIクラスを探す
PS D:\> Get-WmiObject -List -Class "*Disk*"
(出力内容は省略)

#ディスク容量が残り10%を切ったディスクのドライブレターを表示
PS D:\> Get-Wmiobject -class Win32_LogicalDisk | ?{ $_.Size -ne $null } | ?{ $_.FreeSpace / $_.Size -lt 0.1} | %{ $_.DeviceID }
(出力内容は省略)


参考サイト:
WMIクエリでのLIKE演算子の使用

参考コマンド:

Get-Help about_WMI_cmdlets


便利なツール:
Windows PowerShell Scriptomatic

修正履歴:

2010年1月28日木曜日

PowerShellから.NETのアセンブリを呼ぶ

PowerShellを使っていると、
.NETのアセンブリを使いたくなる場面に頻繁に遭遇します。
PowerShellは.NET Framework上で動作するシェル言語ということで、
簡単にアクセスすることができます。

実行例:
.NETのアセンブリを使ってみる。

#System名前空間にあるメソッドには、[クラス名]::でアクセスできる
PS> $array = @("a","b","c")
PS> [String]::Join("|", $array)
#出力 → a|b|c

#System以外の名前空間にあるメソッドへは、[名前空間名.クラス名]::でアクセスできる。
PS> [System.IO.Path]::GetTempPath()
#出力 → TEMPフォルダのパス


#System以外の名前空間にあるメソッドへは、「System.」は省略可能。
#Systemがデフォルト、って事なんだろうか。。。
PS> [IO.Path]::GetTempPath()
#出力 → TEMPフォルダのパス

#System以外の名前空間にあるクラスのインスタンスを作る。
#メソッドの呼び出しと同様、「System.」は省略可能。
PS> $list = New-Object -TypeName "Collection.ArrayList"
PS> $list.GetType().Name
#出力 → ArrayList

#Systemの付かない名前空間にアクセスする場合、アセンブリを予めロードする必要がある。
PS> [Microsoft.VisualBasic.Constants]::vbTab
#エラーパイプライン →
[Microsoft.VisualBasic.Constants]が見つかりません。この型を含むアセンブリが読み込まれていることを確認してください。

PS> [Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic") | Out-Null
PS> [Microsoft.VisualBasic.Constants]::vbAbortRetryIgnore
#出力 → DefaultButton1, OkOnly, AbortRetryIgnore



参考コマンド:

参考リンク:

関連投稿:
PowerShellからWin32APIを使う

修正履歴:
2010/02/02:実装例のソースコードを若干修正。
2010/02/24:関連投稿のリンクを追加

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月23日土曜日

PowerShellでファイルを圧縮!(zipファイル)

システムの運用をしていく中で、
ファイルを圧縮して保存することは、よくある話です。
ログファイルなんかは結構小さくなりますしね。

PowerShell Community Extensionsで提供されるコマンドレットに、
Zipファイルを作成するコマンドレットが含まれますので、
これを使うのが一番簡単じゃないかと思います。

が、この手のモノを使っちゃいけないような場合についても、
Comオブジェクトを使用してさっくりと実装することが可能です。

実装例:
Zipファイルを作成します。
Zipファイルの直下に同一名称のアイテムを配置しようとすると
ポップアップウィンドウが出て困るので、
重複した場合には処理を中止し、zipファイルも消します。

# Zip-Item.ps1
$ZipFilePath = "D:\testzip.zip" # zipファイル作成先
$TargetItems = "D:\test","D:\*.rb" # 圧縮対象ファイルとかフォルダとか

# Zipファイル作成(同一名称ファイルが存在する場合には予め削除する)
if(Test-Path -Path $ZipFilePath)
{
Remove-Item -Path $ZipFilePath
}
([char]80 + [char]75 + [char]5 + [char]6).ToString() + ([char]0).ToString()*18 | `
New-Item -Path $ZipFilePath -Type File

# Zipファイルにファイルおよびフォルダを投入
$shell = New-Object -com Shell.Application
$zipfile = $shell.NameSpace($ZipFilePath)
$archivedItems = New-Object Collections.ArrayList
foreach($item in ($TargetItems | %{Get-Item -Path $_}))
{
# 同一名称のアイテムが存在した場合、zipファイルを削除して例外をスロー
foreach($archivedItem in $archivedItems)
{
if($item.Name -eq $archivedItem.Name)
{
Remove-Item -Path $ZipFilePath
throw "ファイル名またはフォルダ名が重複しています:" `
+ $archivedItem.FullName + "," + $item.FullName
}
}

# ファイルまたはフォルダをZipファイルに投入。圧縮処理は非同期なので、その終了を待つ。
$archivedItems.Add($item) | Out-Null
$zipfile.CopyHere($item.FullName)
while($true)
{
if($archivedItems.Count -eq $zipfile.Items().Count)
{
break
}
Start-Sleep -Seconds 1
}
}
return $archivedItems.ToArray()


参考サイト:
Jeans & Development : VBScript での zip ファイルの作成

修正履歴:
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