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