2011年10月31日月曜日

◆$host.UIのプロンプト入力機能を使う2

前回のPowerShell: ◆$host.UIのプロンプト入力機能を使うと同様のメソッドで「PromptForChoice」メソッドを使ってみた。

使い方はほぼ同様。
選択肢としてFieldDescriptionの代わりにChoiceDescriptionクラスを使う。
このクラスはコンストラクターに文字列を2つ取り、1つが選択肢そのものでもう一つがそれに対応するHELP表示項目(説明項目)になっている。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024

$dClass = [System.Management.Automation.Host.ChoiceDescription]
$cClass = "System.Collections.ObjectModel.Collection"

$descriptions = New-Object "$cClass``1[$dClass]"
$questions =
 
 ((
"&1:巨人"),("読売ジャイアンツ")),(("&2:阪神"),("阪神タイガース")),
 (("&3:中日"),("中日ドラゴンズ")),(("&4:横浜"),("横浜ベイスターズ")),
 (("&5:ヤクルト"),("東京ヤクルトスワローズ")),(("&6:広島"),("広島東洋カープ"))
$questions | %{$descriptions.Add((New-Object $dclass $_))} 
$caption = "<アンケート>" 
$message = "好きな球団はどこですか?"
 
 

$result =  $host.UI.PromptForChoice($caption, $message, $descriptions,2) 
Write-Host $descriptions[$result].Label.replace("&","") "ですね"
$winner = {Get-Random (0..($questions.Count-1) | %{$questions[$_][1]})}
"`n来年以降の優勝予想は"
1..4 | %{invoke-command $winner}
"となっています。"
$winner2 = Set-PSBreakpoint -Variable winner2 -Mode Read -Action
 { 
 
$script:winner2 = Get-Random (0..($questions.Count-1) | 
%{$questions[$_][1]})}
"`n2位予想は"
1..4 | %{$winner2}
"となっています。"

image

選択肢は&の次の文字がショートカットになるようだ。
ISEの場合、ヘルプはツールチップで表示される。

優勝予想に深い意味はありません。(ちょっと試してみた)

あー、2位予想は1位を抜かさないとダメか。(^^;

2011年10月28日金曜日

◆$host.UIのプロンプト入力機能を使う

$host.UIにあるPromptメソッドを調べてみた。

$host.UIのメンバーはこんな感じ。
image

だいたいコマンドレットでもみかけるようなメソッドが並んでいるが、その中でPromptというのは見かけない感じ。

調べてみると、ユーザー入力を受け取るときに使うメソッドのようだ。
ReadLineと違ってこちらは複数項目を続けて受け取れる。

定義を見てみると、
image

なんか見ただけで嫌になってくる(笑)

CaptionとMessageとDescriptionを指定するとDictionaryが返って来る。
CaptionとMessageはStringだから良いとして、DescriptionはCollection ジェネリック クラス (System.Collections.ObjectModel)という事で、ジェネリッククラス。
なので前回調べたジェネリックの作成方法を使う。

また、Collectionの中身がSystem.Management.Automation.Host.FieldDescriptionなのだろうからこれを調べるとString引数をひとつ取るコンストラクターになっている。
どうやら、それがユーザー入力項目の名称になりそうだ。

いろいろ試行錯誤した結果、とりあえず以下のような感じでそれらしいPromptになった。

</
001
002
003
004
005
006
007
008
009
010
011
012
013

$dClass = [System.Management.Automation.Host.FieldDescription]
$cClass = "System.Collections.ObjectModel.Collection"

$descriptions = New-Object "$cClass``1[$dClass]"
$questions = "国名","都道府県名","市区町村名"
$questions | %{$descriptions.Add((New-Object $dclass $_))} 
$caption = "<ユーザー入力>" 
$message = "質問項目に回答願います。"
 
 

$result =  $host.UI.Prompt($caption, $message, $descriptions) 
Write-Host "`n入力確認。" 
$result.GetEnumerator() |
 
 
%{write-host $_.key $_.value -Separator " : "}

image

せっかく苦労したのでもう少し調べてみる。
そもそもこれだけの機能であれば、[System.Management.Automation.Host.FieldDescription]を使わずともStringで良い。
そこでこのクラスのメンバーを見てみると、
image

なにやらDefaultとかも指定できる風。
そこで6行目を以下のようにしてDefaultValueとHelpMessaeを追加してみた。

$questions | %{$descriptions.Add((
  New-Object $dclass $_ -Property @{DefaultValue="未入力";HelpMessage="ヘルプです"}
))}

するとPowerGUIではこんな感じに表示される。
image

予め、値にDefault値がセットされ、HelpMessageがツールチップで表示される。
ん~、なかなか高機能。
せっかく苦労したのだからこれくらいはやってもらわないと、っとちょっと満足しかけたのだが本家PowerShellではこんな表示になってしまう。
image

Defaultは何処へやら・・・。だめだこりゃ!!(ISEもダメ)

PowerGUIの実装に負けているということか・・・。

ならば、IsMandatoryを指定して必須にしちゃうかと思ったがこちらも効かないっぽ。

2011年10月27日木曜日

◆ジェネリックなクラスを使う

PowerShellはもともと厳密な型指定を要求されるわけではないのでジェネリックなクラスが必要になることはあまり無いように思う。

しかし、最近ではジェネリックなクラスでしか提供されない機能もある。
そこで、簡単にその使い方を調べてみた。

ジェネリックの代表格「List」の使い方はこんな感じのようだ。

001
002
003
004
005
006
007
008
009

$genericList = 
   
'System.Collections.Generic.List`1[System.String]'
$list = New-Object 
$genericList
$list2
 = New-Object 
$genericList
$list
.Add("test1")
$list.Add("test2")
$list2.Add("test0")
$list2.AddRange($list)
$list2

Listのクラス名に続けて型パラメータの型を指定してあげる。
指定は文字列になるようだ。
(`1)は型パラメータの個数を指定しているものと思われる。

型パラメータが2個の場合は、xxxxx`2[System.String,System.String]
といった形だろうか。

結果を見るととりあえずOKそう。
image

ただし、このパターンで簡単に使えるのはmscorlibのジェネリッククラスだけで、それ以外はちょっと面倒臭そう。
一応以下にサンプルが載っていたので使えるには使えたが、余程のことが無い限り使うことは無い気がする。
.net - Powershell Generic Collections - Stack Overflow

また、PowerShellクックブックにジェネリックなクラスを使うための関数が載っているのでいざというときは参考にすると良いかも。
Creating Generic Types in PowerShell | Precision Computing

V3を眺めているとジェネリックというキーワードもちらほら見えるのでV3では簡単に使えるようになるのだろうか・・・。


もう少し簡単に以下のようにインスタンシングできるようだ。

>$list = New-Object System.Collections.Generic.List[string]

◆スクリプトファイルのパスを取得する(補足)

PowerShell: ◆スクリプトファイルのパスを取得するで通常は取得可能なのだがFunction中ではうまく取得出来ない。

どうやらFunctionのスコープでもこの変数は設定されていて、それに隠蔽される風。

$script:myInvocation.MyCommand.pathとやれば参照できるようだ。

Functionの外で退避しておけば良い話でもあるのだが・・・。

2011年10月21日金曜日

◆Write-Progressでプログレスバーを表示する

特に実用性は無いが、指定した文字列をHELPから探す関数を使ってプログレスバーの使い方を確認する。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017

function Search-Help
{
   
param($pattern
)
   
   
$helpNames = Get-Help * | ?{$_.Category -ne "Alias"
} 
   
   
foreach($helpTopic in $helpNames
)
    {
       
$content = Get-Help -Full $helpTopic.Name | Out-String
        if($content -match $pattern
)
        {
           
$helpTopic | select name,synopsis
        }
    }
}


Search-Help フォーマット演算子

とりあえず必須パラメータだけを指定して表示するには以下の一行を追加すれば良い。
image

するとISEではバーが何も進まないダイアログを実行中の間だけ表示してくれる。
2011-10-21 19h32_51

また、コマンドラインでは以下のような表示になる。
image

無駄なバーが表示されないのでコマンドラインならこれでも良いかと思う。
ちなみに、PowerGUIで表示されるダイアログには無駄なバーは表示されなかった。

なにも動かないとちょっと寂しいのでこんな感じにしてもよいかも。

Write-Progress -activity "HELP検索"  `
  -Status ("検索中..." + "." * ($counter++ /10)) 

すると、ちょっとずつ進んでくれる。
image

せっかくのプログレスバーなので、その機能も使ってみるとこんな感じ。
PercentCompleteパラメータに現在の進捗率を指定してあげる。

Write-Progress -activity "HELP検索" -Status "検索中..."  `
    -PercentComplete ($pCounter++ * 100 /$helpNames.Count) 

image

CurrentOperationなるパラメータもあって、こいつに現在処理中のファイル名を指定してみる。

Write-Progress -activity "HELP検索" -Status "検索中..."  `
    -PercentComplete ($pCounter++ * 100 /$helpNames.Count) `
    -CurrentOperation ($helpTopic.Name)

インストーラー等でよくあるようにファイル名が意味もなく流れる。
image

SecondsRemainingパラメータを指定すると残り時間も表示できるので適当に計算して指定してみた。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026

function Search-Help
{
   
param($pattern
)
   
   
$helpNames = Get-Help * | ?{$_.Category -ne "Alias"
}
   
$STime = Get-Date
    $pCounter = $remaining = 0
    foreach($helpTopic in $helpNames
)
    {
       
$span = New-TimeSpan $STime (Get-Date
)
       
if($pCounter % 20 -eq 1){$remaining =
 
          (
$span.Seconds / $pCounter ) * ($helpNames.Count - $pCounter
)}
       
Write-Progress -activity "HELP検索" -Status "検索中..."
 `
           
-CurrentOperation ($helpTopic.
Name) `
           
-PercentComplete ($pCounter++ * 100 /$helpNames.
Count) `
           
-SecondsRemaining $remaining
 
           
       
$content = Get-Help -Full $helpTopic.Name | Out-String
        if($content -match $pattern
)
        {
           
$helpTopic | select name,synopsis
        }
    }
}


Search-Help フォーマット演算子
<!--EndFrag

image

IDパラメータというのもあって、こいつに適当な数値をキーとして指定することにより別のバーを追加することができる。(たまに凝ったインストーラーが表示するネストしたバーみたいなことができる)

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045

function Search-Help
{
   
param($pattern
)
   
   
$helpNames = Get-Help * | ?{$_.Category -ne "Alias"} | sort Category
    $helpNames | Group Category |
 
     
%{$CatCount = @{}}{$CatCount.($_.Name) = $_.
Count}
   
   
$STime = Get-Date
    $pCounter = $remaining = 0
    $catStatus = "○" + (($CatCount.Keys | sort) -Join "○"
)
   
   
foreach($helpTopic in $helpNames
)
    {
       
$span = New-TimeSpan $STime (Get-Date
)
       
if($pCounter % 20 -eq 1){$remaining =
 
          (
$span.Seconds / $pCounter ) * ($helpNames.Count - $pCounter
)}
       
Write-Progress -activity "HELP検索(全体)" -Status "検索中..."
  `
           
-CurrentOperation ($helpTopic.
Name) `
           
-PercentComplete ($pCounter++ * 100 /$helpNames.
Count) `
           
-SecondsRemaining $remaining
 
           
       
if($prevCategory -ne $helpTopic.
Category)
        {
           
if($prevCategory
){
             
$catStatus =
 
               
$catStatus -replace ("○"+$prevCategory),("●"+$prevCategory
)
            }
           
$prevCategory = $helpTopic.
Category
           
$cCounter = 0
        }
       
       
Write-Progress -activity "HELP検索(Category)" -Status $catStatus
  `
         
-CurrentOperation ("({0}処理中)" -f $helpTopic.Category) -Id 1
  `
         
-PercentComplete ($cCounter++ * 100 /$CatCount[$helpTopic.Category]
) 
       
       
$content = Get-Help -Full $helpTopic.Name | Out-String
        if($content -match $pattern
)
        {
           
$helpTopic | select name,synopsis
        }
    }
}


Search-Help フォーマット演算子

image

ちょっと調子に乗って余計な処理まで追加したせいもあるが、ここまで来ると本来の処理よりプログレスバーの表示処理のほうが重くなってくる。

ケースバイケースで適切なレベルの表示をしたほうがよさそうだ。

2011年10月19日水曜日

◆Windows PowerShell Command Builder

こんなツールが提供されているようです。
2011-10-19 09h30_06

VerbsもしくはNounsを選択するとそれぞれ対応するVerbs、Nounsが絞りこまれてその組合せを指定できます。
VerbsとNounsが決まるとそれに対応するパラメータが表示されます。

Office用として提供されているようなので対象Productsで選択できるのは、
2011-10-19 09h36_55

といった感じになっていますが、今後他のサーバー用モジュール向けにも提供されると良いですね。(既にあるのかもしれませんが)

ちなみにV3のISEでは画面右側に上記と似たようなペインが表示されて、コマンドを選ぶと、対応したパラメータをGUIで指定できるようになっています。
2011-10-19 09h43_49

こちらは、VerbsとNounsの組合せではなく単純に一覧からコマンドを選択するようです。

なお、こいつはコマンドラインでも

PS>Show-Command Get-ChildItem

などとやると表示されます。
2011-10-19 09h50_15

今のところ、PassThruとかはないので一連のコマンドに組み込むときはコピーして貼り付けるしかなさそうです。

2011年10月18日火曜日

◆ISEをカスタマイズする2

PowerShell: ◆ISEをカスタマイズする(V3)でV3の機能かと思っていたら、現状でも使える機能でした。

PowerGUIを使い始めてから、ISEは全く触ってこなかったがV3になってインテリセンスが使えればISEでも良いかもと思い始めています。

そこで、ISEをもう少しだけカスタマイズして見ました。
前回の機能も含めて、Profileをこんな感じにして見ました。

</TAB
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023

#外部ウィンドウで実行
$script =
 {
   
Start-Process PowerShell -ArgumentList "-NoExit",
                        "$($psISE.CurrentFile.Editor.Text)"
}
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Clear()
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.
Add(
                       
"Run in another window",$script,"Alt+F5") | Out-Null

#出力ウインドウのクリア
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.
Add(
                       
"Clear Windowsw",{cls},"Alt+R") | Out-Null

#プロンプト(Select Current Output)
Function Prompt
{
   
$lc = $psise.CurrentPowerShellTab.Output.
LineCount
   
$psise.CurrentPowerShellTab.Output.Select($myISECount,1,$lc - 1,1
)
   
$psise.CurrentPowerShellTab.Output.EnsureVisible($myISECount
 )
   
$script:myISECount = $lc
    $psise.CurrentPowerShellTab.CommandPane.
Focus()

   
"PS>"
}

通常、出力結果が長い時は出力ウインドウを上にスクロールして先頭から見ることが多い。
そこで、予め出力結果の先頭がウインドウに表示されるようにして見ました。
ついでに、今回の(最新の)出力結果をSelectし反転させています。

2011-10-18 20h25_13

まぁ、なんでもない処理ですが個人的にはちょっと気に入っています。