2010年11月21日日曜日

◆コンピュータにしゃべらせる

これは面白い。使い道ありそう。

001
002
$sam = New-Object -comObject SAPI.SpVoice
$sam.Speak("My name is ($env:username)" )

◆PowerGUIを使う

PowershllISEが出る前に使っていたのだがISEが出てからは使っていなかった。
最近また使い始めたらだいぶパワーアップして実に便利なので紹介しておく。

インストールは何も難しいことはない。
使い勝手としてはPowershellにVisualStudioのインテリセンスがやってきた、という感じであろうか。どうしても起動はちょっと重いが、起動してしまえば快適そのものである。
インストールすると以下の2つのアイコンが登録される。
image
通常はScriptEditorのほうを使ってスクリプトを書いていくことになる。もう一つのほうがはVisualStudioでいうところのソリューションエクスプローラーみたいなものかと思っているが、私は使っていないので詳しくはわからない。(追記:システム管理ツールですね)

コマンドレットを打ち込んでいくと以下のような感じで候補を表示してくれる。
image

シンタックスも表示してくれるのは実にうれしい。まさにインテリセンス並み。
パラメーターもちゃんと表示する。
image

コードスニペット挿入の機能もある。

スクリプトを実行すると変数ウインドウに自動変数なども含めて現在のスコープの変数が表示される。これでを参照すればデバッグ効率は格段に向上する。
image

あと、私が気に入っているのがこのボタン。
image

これは現在のスクリプトを新しいセッションで外部ウインドウとして実行してくれる。ISEを使っていたときは、通常のスクリプトとして実行した時とISEで実行した時の結果が違うことがあるので、いちいち両方でテストするのが面倒だったが、これがあるおかげでクリーンな状態で簡単にテストができる。

私はWindowsLiveWriterを使ってブログを書いているのだが、なにより嬉しかったのがアドオンでWindowsLiveWriterにソースを張り付ける機能が提供されていたこと。
WindowsLiveWriter自体にもソースコード挿入用のアドインはあるのだが、当然Powershell用のシンタックスは用意されていないため色分けとかが効かない。

PowerGUIのアドオンの追加は若干面倒なので(結構悩んだ)ちょっと説明しておく。
image
アドオン用のコミュニティサイトが開くので好きなものを選んでダウンロードする。
今回は試しに以下のBlueConsoleを選んでみた。
解凍して出てきたファイルを以下のフォルダーにコピー。(必ずしもここじゃなくともよいのだとは思うが、ここにしろと書いてあったので)
D:\Documents\WindowsPowerShell
次に以下のメニューを開く。
image

Add Moduleを開く。
image

ここで解凍したフォルダーを選べばOK。

っと説明にはあるのだが、昨日色々試行錯誤していたらいつの間にかうまくいったのだが今日やるとうまくいかない。

そこで、元から存在していたBitsモジュールの場所を探してそこに置いてみたらビンゴ。
C:\Windows\System32\WindowsPowerShell\v1.0\Modules
image

もう一度PowerwGUIに戻ってライブラリーメニューを開くと
image

Add-on.BlueCOnsoleが表示されているのでチェックをつけてOK.
エディター画面に戻ると以下のようにコンソール出力がBlueになっている。
image

会社の環境で試したら、今度は最初の手順でOKだった。
よくわからないが、まぁどちらかの方法で追加できることは確実なようだ。

◆バックグラウンドジョブの終了イベントを受け取る

Powershell2.0ではジョブをバックグラウンドで実行する機能が追加になっていて比較的簡単に使える。

20101121101144 
これらの名前を見ただけでなんとなく想像はつく。
ちなみに、ここら辺の整然としたコマンド体系がPowershellのすばらしいところですね。いまだにDOSのバッチファイルなどでゴニョゴニョしている人はぜひPowershellに移行しましょう

バックグラウンドジョブの基本は以下のサイトで詳しく解説がなされているので参考にすると良いだろう。
PowerShell 2.0の新機能(3) ――バックグラウンドジョブ編(1/5):CodeZine

Start-Jobでジョブを開始し、Get-Jobで状態を確認することができる。終了したらReceive-Jobで結果を取得。となるのだが、Get-Jobを連打して終了を待つのも芸がない。コールバックしてほしいところだ。

というわけでジョブの結果オブジェクトを見てみると、おぉーちゃんとあるじゃないですか
20101121160902

StateChangedイベント。これにイベントハンドラーを登録すれば良さそうだ。
Powershell2.0からRegister-ObjetEventコマンドレットが追加になっており、これで登録が可能だ。Actionパラメータにスクリプトブロックを指定してあげれば良い。

以下のような感じだ。

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
$job = Start-Job { 
Dir $env:windir *.jpg -r  -ea SilentlyContinue} -Name "WinDirの検索"

Register-ObjectEvent $job -EventName StateChanged  `
  -SourceIdentifier JobStateChanged `
  -Action { 
  Write-Host "JOb「$($sender.Name)」が $($event.TimeGenerated) に終了" 
  Write-Host "★結果は★"
  Receive-Job -Id ($sender.id) | Write-Host

  $global:sender1 = $sender
  $global:event1 = $Event
  $global:subscriber = $EventSubscriber
  $global:source = $SourceEventArgs
  $global:SourceArgs1 = $SourceArgs

  Unregister-Event -SourceIdentifier JobStateChanged
}

ヘルプによるとActionパラメータの中では$sender,$evnet,$eventSbuscriber,$sourceEventArgs,$sourceArgsの自動変数が参照可能だとあるが、確認した限りでは最後の2つは何も入ってこなかった。
ちなみに上記ソースでglobal変数に退避しているのは、処理には全く関係なく後から情報を調べたかったためだ。バックグラウンドジョブなのでglobalスコープにしておかないと後から参照できない。

また、ここでは発生するイベントをCompletedだと決め打ちして処理しているが実際にはイベントの種類を判定して処理を振り分ける必要がある。イベントには以下のようなものがある。

JobState Enumeration (System.Management.Automation)

Blocked The job is blocked, such as waiting for user input, from running the commands of the pipeline in one or more runspaces. This field is introduced in Windows PowerShell 2.0.
Completed The job has successfully run the commands of the pipeline in all runspaces. This field is introduced in Windows PowerShell 2.0.
Failed The job was not able to successfully run the commands of the pipeline. This field is introduced in Windows PowerShell 2.0.
NotStarted The job has not begun to run the commands of the pipeline. This field is introduced in Windows PowerShell 2.0.
Running The job is in the process of running the commands of the pipeline. This field is introduced in Windows PowerShell 2.0.
Stopped The job has been cancelled on one or more runspace. This field is introduced in Windows PowerShell 2.0.

ちょっとここで考えるのがCompletedとそれ以外だけで判定して良いのかという事である。
たとえば、Blockedで始まりそのあとにRunningになるなんてことがないのかどうか。
ここら辺は資料を見つけられなかったので実際に試しながらとなるのかもしれない。
システムに負荷を掛けて何度か試した限りにおいては大丈夫そうではあった。

ちなみにこのスクリプトの結果は以下の通り。
20101121164341

これを見るとイベントハンドラーに登録した処理もバックグラウンドジョブとして登録されているのが判る。

2010年11月20日土曜日

◆変数の型

Powershellの変数はかなり柔軟だ。
Powershellで厳密な型付けにこだわるのはナンセンスというもの。キャストだろうがボクシングだろうがどうぞご自由に?。

しかし、一応型付けもできるということは知っておいたほうが良いだろう。

PS>$a = 1
PS>$a.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

PS>$a = "1"
PS>$a.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object

PS>[int]$a = 1
PS>$a.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

PS>$a = "A"
値 "A" を型 "System.Int32" に変換できません。エラー: "Input string was not in a
発生場所 行:1 文字:3
+ $a <<<<  = "A"
    + CategoryInfo          : MetadataError: (:) []、ArgumentTransformationMeta
    + FullyQualifiedErrorId : RuntimeException

PS>$arr = 1,2,3
PS>$arr.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS>$arr = [int[]]1,2,3
PS>$arr.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS>$arr = [int[]](1,2,3)
PS>$arr.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32[]                                  System.Array

PS>$arr += 4
PS>$arr.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS>[int[]]$arr = 1,2,3
PS>$arr.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32[]                                  System.Array

PS>$arr += 4
PS>$arr.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32[]                                  System.Array

[int[]]1,2,3 がobject配列になっているのは型指定が1にしか効いていないからだろう。括弧で囲めばint配列になってくれる。
しかし、4を追加した時点でobject配列になってしまう。
確実にint配列にしたいときは、
[int[]]$arr = 1,2,3 こんな感じで。


◆高度な関数機能を使う

Powershell2.0から、それまではコマンドレットでしかできなかったような(コマンドレットのような)関数を作る機能がサポートされた。

PS>function greatly-func {
>>     [CmdletBinding()]
>>     param(
>>         $name,
>>         $age,
>>         $addr
>>     )
>>
>>     $PSCmdlet.MyInvocation.BoundParameters
>>
>> }
>>
PS>greatly-func -addr NewYork -age 87

Key                                                         Value
---                                                         -----
addr                                                        NewYork
age                                                         87

[CmdletBinding()]属性をパラメータにつける事でいろいろなことができるようになる。
ここでは単にこの属性を付与することで追加されるPSCmdlet自動変数を参照している。
この変数はスクリプトに関する様々な情報を保持している。

上記では指定されたパラメータが連想配列として保持されていることが判る。

◆テキストファイルから空行を取り除く

以下のようなテキストから空行(改行とかスペースとかタブとかだけの行)を取り除く。

aaaaaa
      
aaaaaa
  ;      
aaaaaa
#    
aaaaaaa

bbbbbbb   

PS>Select-String -Pattern "\S" -Path test.txt | %{$_.line}
aaaaaa
aaaaaa
  ;
aaaaaa
#
aaaaaaa
bbbbbbb

正規表現のSは大文字。小文字になると反対の意味になるので注意。

記号だけの行も取り除くには、

PS>Select-String -Pattern "\w" -Path test.txt | %{$_.line}
aaaaaa
aaaaaa
aaaaaa
aaaaaaa
bbbbbbb

◆Pauseの実装2

以前PowerShell: ◆Pauseの実装にてDOSのPauseに相当する関数を作ってみたのだが、こいつはISE環境で使うとフリーズしてしまうことが判った。
これでは何かと使いづらいのでちょっと改良してみることにした。

自動変数$ExecutionContextを使うと実行環境が判定できるのISEの場合は単純にMessageBoxを表示することとした。
function Pause
{
    if($ExecutionContext.Host.name -match "console"){
        Write-Host "続行するには何かキーを押してください . . ." -NoNewLine
        [Console]::ReadKey($true) | Out-Null
        Write-Host
    }else{   
        #Add-Type -AssemblyName System.Windows.Forms
        [WIndows.Forms.MessageBox]::Show("続行するにはOKを押してください") | Out-Null
    }
}
あとはこれをProfileの登録すれば良いのだが、ConsoleとISEの両方で使いたいので以下に登録した。
$env:windir\System32\WindowsPowerShell\v1.0\profile.ps1

<追記>
$ExecutionContext.Hostは$hostでも良さげ。