2016/01/03

PowerShellのパイプライン処理

まず、PowerShellのパイプライン処理について軽くおさらいします。

例えば、@、A、Bをそれぞれ何らかのコマンドとしたとき、

@|A|B

というパイプラインがあったら、処理の流れは、

@begin→Abegin→Bbegin→(@process→Aprocess→Bprocess→)×n→@end→Aend→Bend

の順に実行されます。(processブロックで「1入力に対し1出力する」場合以外は必ずしもこうならないですが)

さて、AかBのprocessブロック実行中に、何らかの条件を満たした時、パイプラインのprocessの後続処理を打ち切りたい場合はどうすれば良いでしょうか。

まずはbreakを使った駄目な例

ネットでよく見かける以下のようなコード、すなわち「パイプラインはbreakで処理を打ち切ることができる」というのは実は正しくないのです。

function Select-WhileTest
{
    [CmdletBinding()]             
    param (             
        [parameter(ValueFromPipeline=$true)] 
        [psobject[]]$InputObject,
        [parameter(Position=0)]           
        [scriptblock]$Predicate
    )

    process
    {
        if(!(&$Predicate))
        {
            break
        }
        $InputObject
    }
}

このコードはv2までではそもそも正しく動作しませんが、v3以降では条件によっては正しく動作しているように見えるのが、誤解の元なのかと思います。(というか私も誤解してました。)

例えば、

$result = "初期値"
$result = &{end{foreach($i in (1..5)){$i}}} | Select-WhileTest {$_ -lt 3}
Write-Host "`$resultは $result です。"

のようにすると、

$resultは 1 2 です。

のように、想定した通りの結果が得られます。このように、上流のスクリプトブロックのendブロック内にforeachなどループブロックが存在し、そのループブロック内で下流に値を出力している場合はうまくいきます。(ちなみに、スクリプトブロック直下に記述するのとendブロック内に記述するのは等価。)

しかし、上流にループブロックがない場合、例えば

$result = "初期値"
$result = 1..5 | Select-WhileTest {$_ -lt 3}
Write-Host "`$resultは $result です。"

とすると、コンソールに1と2が改行区切りで表示されますが、ホストに表示されるだけで$resultには値は格納されません。そしてスクリプト化して実行した場合は、Write-Hostが実行されることすらなく、スクリプトが終了してしまいます。

breakだとなぜうまくいかないのか

結局どういうことかというと、パイプライン下流のbreakは、パイプラインを打ち切る処理をするのではなく、単に一つ上流のブロックをbreakする処理に過ぎないのです。

パイプライン上流にループブロックがある場合は、そのループブロックをbreakしますが、それ以外の場合はスクリプトのbegin, process, endのいずれかのブロックがbreakされてしまい、結果としてスクリプトが終了してしまうわけですね。

そして、このSelect-WhileTest関数だと大丈夫ですが、processブロックの中にループブロックを記述し、その中でbreakを書くのは当然ダメです。単にそのループを抜けるだけなので、上流の出力は止まってくれません。

breakではなくcontinueを使う場合も基本は同じ結果です。しかもcontinueは所詮、その名の通り継続処理なので、上流に以下のような無限リストがあると無限ループに陥ってしまいます。

&{begin{$i = 0} process{while($true){$i++; $i}}}|Select-WhileTest {$_ -lt 3}

breakの代わりに、

throw (New-Object System.Management.Automation.PipelineStoppedException)

を実行する方法も見かけますが、これはループブロックがあっても強制的にスクリプトが終了するので余計ダメです。try...catchでエラートラップすればスクリプトの終了は回避できますが、「パイプラインが正常終了せずエラーを出した」扱いであることには代わりないので、やはり出力を変数に格納することができません。

ダミーループを用いる、取りあえずの解決策

前述のbreakを使った方法の問題点のうち、上流にループブロックがないとスクリプトが終了してしまい、出力を変数に代入することもできない問題は、とりあえず解決する方法があります。

以下のように、呼び出す時にパイプライン全体をダミーのループブロックでラップすれば良いのです。

$result = "初期値"
$result = do{
    1..5 | Select-WhileTest {$_ -lt 3}
}until($true)
Write-Host "`$resultは $result です。"

このようにしておけば、breakはパイプラインの外側のdo...untilを抜ける効果になるので、スクリプトが終了する心配も、値を出力しない問題も起こりません。

元々、パイプライン上流にループブロックが存在する場合でも、単にdoループ内の処理が1回走るだけなので、特に問題は起きません。1回だけ処理を実行するダミーループなら、for($i=0; $i -lt 1; $i++){}とかでも良いです。

ただ…この記述を美しいと思う人は多分いないですね。事情を知らないと意味不明ですし。そして、breakを記述する側の関数には、前述の通りのループブロック内では値を出力できないという制限は残ったままになります。

やはりbreakでパイプラインを打ち切るのは、本来想定された動作かと言われるとかなり微妙なところだと思います。(v3で一応動くようになったとはいえ)

この方法についての参考記事:Cancelling a Pipeline - Dreaming in PowerShell - PowerShell.com ? PowerShell Scripts, Tips, Forums, and Resources (ただしv2準拠の内容であることに注意)

ところで、Select-Object -Firstは…

さて、話は変わって、PowerShell 3.0からはSelect-Object -First の処理が変わったことについては、ご存知の方も多いかと思います。

具体的には、v2までは単にパイプライン処理をすべて終了してから、最初のn件を抽出する処理であったn件のパイプライン出力がされた後は、入力をすべてフィルタし出力に流さなくなる動作であったところが、v3からはn件のパイプライン出力がされた時点で、パイプラインの処理を打ち切るようになりました。(1/5修正)

つまり、

$result = 1..5| &{process{Write-Host "Process:$_"; $_}}| Select-Object -First 2
Write-Host "`$resultは $result です。"

というスクリプトは、v2までは

Process:1
Process:2
Process:3
Process:4
Process:5
$resultは 1 2 です。

のようにパイプライン出力は指示通り2件であるものの、上流の処理は結局、全部実行されてしまっています。

一方v3以降では、

Process:1
Process:2
$resultは 1 2 です。

のように、きちんと上流の処理を打ち切ってくれています。

つまり、ここまで述べてきたパイプライン処理の打ち切りは、実はv3以降のSelect-Object -Firstでは実現できているということです。これと同じことを我々も自作関数の中でやりたいわけです。

ではSelect-Object -Firstは具体的にどういう処理をしているかというと、StopUpstreamCommandsExceptionという例外をthrowすることでパイプライン処理の打ち切りを実現しています。この例外はまさに名前の通り、パイプライン上流の処理を中止するための例外です。この例外を自作関数でthrowしてやればうまくいきそうです。

しかし、この例外は非publicな例外クラスであることから、New-Objectコマンドレットなどでインスタンス化することはできません。リフレクションを駆使する必要がでてきます。
参考:PowerShell 3.0からはじめるTakeWhile - めらんこーど地階

(1/5追記)参考2:パイプラインの処理を途中で打ち切る方法のPowerShell版 - tech.guitarrapc.cóm(Add-TypeでC#経由でリフレクションしてます。)

頑張ればできなくはないですが、もっと楽な方法はないものか…と思ってあきらめかけたところ、いい方法を思いついたので紹介します。

Select-Object -Firstを利用する方法

Select-Object -Firstでできることが我々には(簡単には)できない。ならばどうするか。Select-Object -Firstを使えばいいじゃない。という発想です。

function Select-While
{
    [CmdletBinding()]             
    param (             
        [parameter(ValueFromPipeline=$true)] 
        [psobject[]]$InputObject,
        [parameter(Position=0)]           
        [scriptblock]$Predicate
    )

    begin
    {
        $steppablePipeline = {Select-Object -First 1}.GetSteppablePipeline()
        $steppablePipeline.Begin($true)
    }

    process
    {
        if(!(&$Predicate))
        {
            $steppablePipeline.Process($InputObject)
        }
        $InputObject
    }

    end
    {
        $steppablePipeline.End()
    }
}

scriptblockにはGetSteppablePipelineというメソッドが存在し、このメソッドによりSteppablePipelineオブジェクトが取得できます。これは何かというと、要は「スクリプトブロック内のbegin, process, endを個別に実行する」ための機能です。
参考:PowerShell: ◆パイプライン入力・パラメータ入力対応のGridView出力関数を作る(私自身も以前この記事で知りました。)

{Select-Object -First 1}というスクリプトブロックは、1回目に実行するprocessブロック内でStopUpstreamCommandsExceptionを出してくれます。

よって、自作関数のprocessブロック内のパイプライン処理を打ち切りたい箇所で、SteppablePipelineオブジェクトのProcessメソッドを使うことで、{Select-Object -First 1}のprocessブロックの処理を呼び出してやればいいわけです。

このようにして作成したSelect-While関数を以下のように実行してみると、

# 上流にループあり
$result1 = &{end{foreach($i in (1..5)){$i}}} | Select-WhileTest {$_ -lt 2}
Write-Host "`$result1は $result1 です。"

# 上流にループなし
$result2 =  1..5 | Select-While {$_ -lt 3}
Write-Host "`$result2は $result2 です。"

# 上流に無限リスト
$result3 = &{begin{$i = 0} process{while($true){$i++; $i}}} | Select-While {$_ -lt 4}
Write-Host "`$result3は $result3 です。"

結果は

$result1は 1 です。
$result2は 1 2 です。
$result3は 1 2 3 です。

となり、少なくとも今まで述べてきた諸問題はすべて解消していることが分かると思います。

このSelect-While関数は、スクリプトブロックで指定した条件を満たさなくなったときに、パイプライン処理を打ち切ってくれるものですが、この「Steppable Select -First 方式」を使えば他の自作関数でも、割と気楽に呼べるんじゃないかなと思います。ループブロック内で呼び出すことももちろん可能です。

ただし問題点はある

これはSelect-Object -FirstというかStopUpstreamCommandsExceptionあるいはPowerShellのパイプライン機構の仕様に由来する問題であると思われるので、どうにもならないことではあるんですが、一点だけ注意事項があります。

$result = 1..5| &{
    begin
    {
        Write-Host "Begin"
    }
    process
    {
        Write-Host "Process:$_"
        $_
    }
    end
    {
        Write-Host "End"
    }
}| Select-While {$_ -lt 2}

Write-Host "`$resultは $result です。"

これの結果は

Begin
Process:1
Process:2
$resultは 1 です。

となり、なんとendブロックが実行されていません。Select-While {$_ -lt 2} の代わりに Select-Object -Firstを使っても、同様にendは実行されません。

つまり、StopUpstreamCommandsExceptionというのはパイプライン処理を打ち切って、そこまでの出力値を正しくパイプライン出力として出してくれますが、やってくれるのはそこまでで、最後のendブロック処理はしてくれません。

これは十分注意が必要な点で、自作関数内でbeginブロックで確保したリソースをprocessブロックで利用して、endブロックで解放する…という、いかにも書いてしまいそうなパターンは、実はNGなんですね。何も上のようにマニアックなことをしなくても、単に下流でSelect-Object -Firstを使うだけでアウトです。

じゃあ、リソースの取り回しはどうするのが良いの、って話もありますが、それはまたの機会にしましょう。

(1/5追記)あえとすさんの記事が参考になります。:パイプライン処理の後始末をしよう - 鷲ノ巣 ただ、この方法ではパイプライン下流でthrowされた場合はトラップできないぽいですね。コマンドレットクラスの場合はIDisposable実装で良さそうです。

ここからは私見ですが、StopUpstreamCommandsExceptionが後付けかつ非パブリックなところとか、パイプラインを合法的に脱出するステートメントが今に至るまで用意されていないところとか、パイプラインを何とかして途中で打ち切っても、endは実行されないところとかを見ていると、そもそもPowerShellではパイプライン処理の中断というのは、あまり想定してない操作なのかなぁ、という気がしてきています。

上記のような裏技を使って回避するのも一案ではあるとは思いますが、そもそも「パイプライン処理の中断はイレギュラー」と考えて、そういう処理は避けて、必要に応じて別のアプローチを取ることも考えた方がいいのかもしれません。

2012/01/13

コンピュータの再起動はRestart-Computerコマンドレット、シャットダウンはStop-Computerコマンドレットを使えばできますが、(休止状態ではなく)スリープはどうやるんだろう?と疑問に思い調査しました。

  • COMコンポーネントのShell.ApplicationのSuspendメソッドを使う方法:
    少なくともうちのWin7環境では使えませんでした(何も起こらない)。
  • shutdown.exeを使う方法:
    /hオプションを使えば休止状態にはできるが、スリープはできない模様。
  • rundll32.exe powrprof.dll,SetSuspendStateを使う方法:
    ハイブリッドスリープが有効である場合は休止状態になる。ちなみにネットでたまにみかける”rundll32.exe powrprof.dll,SetSuspendState Sleep”でスリープするというのは嘘tipsです。
    参照:rundll32.exe powrprof.dll,SetSuspendState Sleepって大嘘は誰が言い出したん - xcaqhbajのメモ
  • WMIのWin32_OperatingSystemのWin32Shutdownメソッドを使う方法:
    シャットダウン、再起動、ログオフはできますがスリープや休止状態はできないようです。

というわけであまり簡単にはいかないようです。

幸いPowerShell 2.0からはAdd-TypeコマンドレットによりC#やVBを介してP/Invokeができますので、これを利用してスリープを行うWin32APIを呼び出すことにしました。

$signature = @"
[DllImport("powrprof.dll")]
public static extern bool SetSuspendState(bool Hibernate,bool ForceCritical,bool DisableWakeEvent);
"@
$func = Add-Type -memberDefinition $signature -namespace "Win32Functions" -name "SetSuspendStateFunction" -passThru 
$func::SetSuspendState($false,$false,$false) 

powrprof.dllに含まれるSetSuspendState関数をP/Invokeで呼び出してやります。引数のHibernateはTrueを指定すると休止状態、Falseを指定するとスリープになります。今回はスリープしたいので$falseを渡してやります。

あとで知ったのですがSystem.Windows.Forms.Application.SetSuspendState メソッドを使うのでもいいですね。こちらの場合はAdd-TypeコマンドレットでSystem.Windows.Formsを読み込むと利用できるかと思います。

Add-TypeコマンドレットのおかげでPowerShellからWin32APIを簡単に呼べるようになり、○○はWin32APIを使わないといけないから諦めよう、ということがなくなりました。WSH時代に比べると良くなったなーと思います。本当はなるべくならWin32APIを直接は使わずに片づけたいところですけども。

2011/12/02

はじめに

このたび、技術系アドベントカレンダーイベントの1つとして、PowerShell Advent Calendar 2011を企画しました。この記事はその2日目の記事となります。アドベントカレンダーについてはリンク先を参照してください。

今日のテーマはPowerShellのバックグラウンドジョブ機能の使い方についてのまとめです。

バックグラウンドジョブとは

バックグラウンドジョブ機能はその名の通り、ジョブ(具体的にはスクリプト)をバックグラウンドで非同期に実行するものです。PowerShell v2で追加された機能の一つです。インタラクティブシェルでStart-Jobコマンドレットを使用してバックグラウンドジョブ(以下、単に「ジョブ」と表記)を実行すると、新しくpowershell.exeのプロセスが起動しそのままシェルに制御が戻りユーザーは後続の処理を行うことができます。もちろんスクリプトからジョブを実行することも可能です。時間のかかる処理をバックグラウンドで走らせたり、数多くの処理を並列で実行したりするのに重宝します。

起動されたジョブは操作中のpowershell.exeとは別のジョブ用のプロセスで実行され、処理が完了すると呼び出し元でその結果をReceive-Jobコマンドレットを使って受け取ることができます。ジョブは並列して何個も同時に実行できます。なおPowerShellのジョブは1ジョブ=1プロセスです。スレッドではないので注意。

PowerShellのジョブシステムはリモート処理インフラストラクチャの上に構築されているので、たとえローカルPCでもジョブ実行するにはローカルPCをリモート用構成にしておく必要があります。詳しくはabout_Remote_Requirementsを参照のこと。

ジョブはローカルでもリモートでも走らせることができます。以下に具体的な方法を述べていきます。

ローカルコンピュータでのジョブ実行

ローカルコンピュータ上に新しくジョブを作成して開始するにはStart-Jobコマンドレットを用います。

Start-Job {ジョブとして実行したいコマンド、スクリプト}

とするとジョブを実行します。

$job=Start-Job {..}

のようにするとJobオブジェクト(System.Management.Automation.PSRemotingJob)を変数に格納してあとで利用できます。変数で受けない場合はJobオブジェクトの内容が表示されます。

存在するジョブを取得するにはGet-Jobコマンドレットを用います。

Get-Job

で現在実行中のジョブ一覧を表示します。以下に出力例を示します。

Id              Name            State      HasMoreData     Location             Command
--              ----            -----      -----------     --------             -------
1               Job1            Completed  True            localhost            "test"
3               Job3            Running    True            localhost            start-sleep -sec 120;"...

以下の表は各項目の意味です。

Id ジョブID番号
Name ジョブの名前
State

Running=実行中のジョブ

Stopped=停止したジョブ

Complete=完了したジョブ

Failed=エラーが出たジョブ

HasMoreData 返却されたデータがあるかどうか
Location ジョブが実行されているコンピュータ名
Command ジョブで実行されているコマンド、スクリプト

ジョブの終了を待つにはWait-Jobコマンドレットを用います。

Get-Job|Wait-Job

とすると実行中のジョブすべてが完了するまで待ちます。-timeoutパラメータを使うと最大待ち時間(秒)を指定できます。

Get-Job|Wait-Job -any

とすると実行中のいずれかのジョブが完了するまで待ちます。正確には「対象のジョブが一つ以上完了するまで待つ」という効果なので、完了済みのジョブが1つ以上ある場合に新たにジョブを追加した場合などは想定の動作になりません。あらかじめRemove-Jobで完了済みのジョブを削除するか、Where-ObjectコマンドレットでRunningのみ対象にするようフィルタをかけるかしてください。

ジョブを中止するにはStop-Jobコマンドレットを用います。

Get-Job -id 1|Stop-Job

とするとジョブIDが1のジョブを中止します。

$jobにJobオブジェクトが格納されている場合は

$job|Stop-Job

でもOKです。

ジョブを削除するにはRemove-Jobコマンドレットを用います。

Get-Job|where {$_.state -eq "Completed" -or $_.state -eq "Stopped"}|Remove-Job

とすると完了済みと中止したジョブを削除します。実行中のジョブは削除できませんが-forceパラメータを使って強制削除することは可能です。

ジョブの実行結果データを取得するにはReceive-Jobコマンドレットを用います。

Get-Job|Receive-Job

とすると完了済みのジョブのうち、結果を返却しているもの(HasMoreDataがTrueのジョブ)があればその結果を表示します。-keepパラメータをつければ結果データを保持しますが付けてない場合は参照後破棄します。

*-Job系のコマンドレットの多くはJobオブジェクトを返却するので、パイプラインでどんどん繋げていけます。

Get-Job|Wait-Job -timeout 10|Receive-Job

のように。

ジョブの基本的な使い方に関して詳しくはabout_jobsを参照してください。

イベントサブスクライブ

PowerShell 2.0では.NET Frameworkのオブジェクトのイベントをサブスクライブすることができます。すなわちイベントハンドラを記述することができます。このイベントサブスクライブ機能もジョブ機能を元に構築されています。

たとえばTimerオブジェクトのElapsedイベントをサブスクライブし、タイマーの実行間隔(ここでは1秒)ごとにtest.txtファイルに乱数を追記していくサンプルは次のようになります。

$timer=new-object System.Timers.Timer
$timer.Interval=1000
Register-ObjectEvent -EventName Elapsed -SourceIdentifier test -Action {get-random|add-content c:\users\daisuke\test.txt} -InputObject $timer
$timer.Enabled=$true

Register-ObjectEventの結果、新しくジョブが生成しそのJobオブジェクトが返却されます。このジョブは-EventNameパラメータで指定したイベントが発生するたび、-Actionパラメータで指定したスクリプトブロックを実行します。

なお、イベントサブスクライブを解除するには

Unregister-Event test

のように-SourceIdentifierパラメータで指定した値を指定してUnregister-Eventコマンドレットを実行することで可能です。サブスクライブを解除してもジョブ自体は削除されない(StateがStoppedになるだけ)ので、必要であればRemove-Jobで削除します。

なお.NETオブジェクトの他にPowerShellスクリプトのカスタムイベント(Register-EngineEvent)、WMIオブジェクトのイベント(Register-WmiEvent)をサブスクライブすることもできます。これらのコマンドレットも同様にイベント発生時の処理をジョブとして登録します。詳しくは各コマンドレットのヘルプを参照してください。

リモートコンピュータでのジョブ実行

最初に述べたとおりPowerShellのジョブ機能はリモートインフラストラクチャの上に構築されています。よってローカルのみならずリモートコンピュータに対してジョブを実行することができます。もちろんリモートコンピュータにもリモート構成されていることが条件です。

基本はInvoke-Commandコマンドレットを用い、

$job=Invoke-Command -ComputerName リモートコンピュータ名 {リモートで実行するコマンド、スクリプト} -asjob

となります。これで{}内の処理がリモートコンピュータ上のPowerShellインスタンスで実行されます。-asJobパラメータをつけることでジョブとして(ローカルPCから見て)非同期に処理できますが、-asJobパラメータを省略すると同期的に実行されます。この場合ジョブは作成されず、リモートでの処理が終了するまでローカル側は待機することになります。

リモートコンピュータに接続するための資格情報を別途入力する必要がある場合は-credentialパラメータを使用します。

Invoke-Command -ComputerName リモートコンピュータ名 {リモートで実行するコマンド、スクリプト} -asjob -credential ユーザー名

とするとパスワードを入力するダイアログが表示されます。なお、スクリプトで動かすときなどあらかじめ入力したパスワードを指定したい場合の方法は以前書きました

同じコマンドを複数のリモートPCで同時実行することも可能で、その場合は-computerNameパラメータにリモートコンピュータ名の配列を指定します(「,」区切り)。この場合ローカルPCで見えるジョブとしては1つですが、そのジョブにリモートコンピュータの数だけ子ジョブ(ChildJobs)が作成されています。

このように子ジョブが複数ある場合にReceive-Jobするときは

$job|Receive-Job -location リモートコンピュータ名

あるいは

$job.ChildJobs

として表示される子ジョブの名前(Name)を調べ、

Receive-Job -name 子ジョブの名前

とすることでリモートコンピュータごとに結果を取得できます。

すべての結果をまとめて取得するなら

Receive-Job $job

とします。

$job|Receive-Jobはなぜか駄目なようです。

固定セッションを用いたリモーティング

同じリモートPCに対して何度もコマンドを実行させたい場合、毎回リモートコンピュータ名を指定してセッションを張るのは非効率的なので、リモートセッションを確立したあとその固定セッションを何度も使用する方法が用意されています。新しく固定セッションを確立するにはNew-PSSessionコマンドレットを用い、

$session=New-PSSession リモートコンピュータ名

とすると固定セッションが確立され、$session変数にそのセッションオブジェクトが格納されます。あとは

Invoke-Command $session {リモートで実行するコマンド、スクリプト} -asjob

とすればそのたびにそのセッションを用いてリモートでコマンドを実行できるようになります。

ここまでの説明はリモートコンピュータでしてきましたが、ローカルコンピュータに対して固定セッションを張ることも可能です。

さらに、Enter-PSSessionコマンドレットを用いると作成したセッションに入ってリモートコンピュータ上のPowerShellを対話実行することも可能です。

Enter-PSSession $session

とすると、プロンプトが

PS カレントディレクトリ>

から

[リモートコンピュータ名]: PS カレントディレクトリ> 

に変化し、以降リモートのPowerShellをローカルPCから対話実行できます。

なおこの状態から抜けるにはexitもしくはExit-PSSessionと入力して実行します。

ジョブ実行できるそのほかのコマンドレット

これまで述べたコマンドレット以外にも、いくつかのコマンドレットはジョブ実行(ローカルorリモート)することができます。ジョブ実行するには-asJobパラメータを使用します。以下にv2の段階で-asJobパラメータが定義されているそのほかのコマンドレットを示します。

これらのコマンドレットはコマンドレット自体にジョブ実行機能がついているので、単独で実行するだけならStart-JobやInvoke-Commandを用いる必要がありません。v2ではWMIを扱うコマンドレットにのみ-asJobパラメータが存在するようです(ここに挙げたコマンドレットはすべてWMIの機能を呼び出すもの)。なお、-asJobパラメータが使用できるコマンドレットの一覧を取得するのに、fsugiyamaさんの1日目の記事の問15のスクリプトを使用させていただきました。

おわりに

PowerShell Advent Calendar 2011二日目は、PowerShellのバックグラウンドジョブ機能概要についてまとめてみました。実はバックグランドジョブ機能のTipsを書こうと思ってその前ふりとして書き始めたのですが、これだけでかなりの量になってしまったので概要だけ一記事としてまとめることにしました。おそらくPSアドベントカレンダーに私はあと何回か登場することになりそうですので、Tips編はその際に書こうと思います。

さて、明日三日目は@jsakamotoさんのご登場ですね。よろしくお願いします!

そして参加者はまだまだ募集中ですよ!→PowerShell Advent Calendar 2011

2011/11/14

PowerShell Advent Calendar 2011というイベントを12/1より開催します。
こちら:PowerShell Advent Calendar 2011 : ATND

現在、参加者募集中です。

以下はイベント告知ページのコピペです。

概要

今年はPowerShellもAdvent Calendarをやりましょう!
Advent Calendarとは技術系コミュニティで行われるイベントの一つで、ある特定技術分野をテーマに、12/1から12/25までの25日間、参加者が交代で毎日ブログ記事を書いてクリスマスを迎えるイベントです。

ルール

PowerShell Advent Calendar 2011ではその名の通り、Windows PowerShellに関する記事を参加者みんなで書いていきます。Tips、コード、等々、PowerShellに関する内容であれば何でもOKです。基本的にATNDの参加表明順が執筆担当日になります。たとえば5番目に参加表明した方は12/5の記事を執筆します。参加者の方は担当日に記事をご自分のブログに書いていただき、そのURLをコメント欄に書いてください。都合で担当日に投稿できない、担当日を変えてほしいなどがありましたらコメント欄でお願いします。

参加者が25人に達した時点で締切りです。逆に期日までに参加者が現れない場合は、すでに執筆した参加者が再度執筆しても良いこととします(その場合はコメント欄で宣言を)。

あなたの参加、お待ちしています!
ぜひがんばって完走させましょう!

参考記事

ここまでコピペでした。

まだ参加希望者が少ないのですが、ぜひぜひ、ご参加くださいませ。お題はPowerShellがからんでいれば何でもOKです。PowerShellを触ってみた感想とか、試しにこういうコマンドを実行してみたorコードを試しに書いてみた、など何でも結構です。Advent Calendarはゆるふわなイベントなのであまり身構えず気軽にご参加いただけると嬉しいです。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2011/11/14/212019.aspx

2011/03/25

PowerShellは一次元のobject配列を多用しますが、実は他言語と同様に、多次元配列やジャグ配列(配列の配列)もちゃんとあります。

ジャグ配列

ジャグ配列の作成

PS > $array = @(@("a","b"),@("c","d","e"))

配列の参照

PS > $array
a
b
c
d
e
PS >  $array.Length
2
PS >  $array[0]
a
b
PS >  $array[0][1]
b

ジャグ配列の作成は、配列をまず作って、その配列を要素に持つ配列を作成するとできます。とても直感的かと思います。ただしここで注意しなければならない点は、

PS > $array = @(@("a","b") + @("c","d","e"))

のように、配列を+演算子で連結するのは駄目であるという点です。こうしてしまうと単に一次元配列同士が連結された要素数5の一次元配列が生成されてしまいます。

さて、このようにジャグ配列に含まれる配列とその数があらかじめ分かっている場合はこのように,演算子を使えば問題ありませんが、そうではない場合はちょっと工夫が必要です。たとえば「テキストファイルの一行ごとにchar[]配列を作り、それらを要素に持つジャグ配列を作成する」ことを考えてみましょう。まず最初に駄目な例。

$lines = @(Get-Content file.txt)
$array = @()
foreach($line in $lines)
{
	$array += [char[]]$line
}

これは一見うまくいきそうですが、先ほどと同じパターンで$arrayは単なるchar[]の一次元配列になってしまいます。目的のジャグ配列を得るには次のようにします。

$lines = @(Get-Content file.txt)
$array = @()
foreach($line in $lines)
{
	$array += ,[char[]]$line
}

ここでは配列要素の追加処理の際、配列に「,」を前置することによって「配列要素を展開することなく、配列そのものとして扱う」ようにしています。こうすることで$arrayにはchar[]配列そのものが要素として追加され、結果としてジャグ配列が格納されます。

 

多次元配列

2x2の多次元配列を作成

PS > $array = New-Object "object[,]" 2,2

要素に値を代入

PS > $array[0,0] = "a"
PS > $array[0,1] = "b"
PS > $array[1,0] = "c"
PS > $array[1,1] = "d"

配列の参照

PS > $array
a
b
c
d

配列のスライス

PS > $array[@(0,0)]
a
PS > $array[@(0,0),@(0,1)]
a
b
PS > $array[@(0,0),@(1,0)]
a
c

配列の作成の仕方がちょっと気持ち悪いですが、これはサイズ固定の一次元配列を作るときと同じ要領です。

配列のスライスも可能で、切り出したい要素のインデックスを「要素のインデックスを表す配列」で指定します。たとえば$array[0,0]を取り出したい場合は$array[@(0,0)]になるわけですね。複数の要素を切り出したい場合は「要素のインデックスを表す配列」の配列(つまりはジャグ配列)を指定することになります。

 

おまけ:一次元配列のちょっとしたtips

変数、プロパティ、コマンドレットの戻り値などで、その値が配列かそうでないかが事前に分からない場合でも、必ず配列として処理したい場合には@を用います。

$lines = Get-Content file.txt

Get-Contentコマンドレットはテキストファイルが1行の場合は、$linesには文字列の配列ではなく文字列が格納されます(複数行なら行ごとの文字列が格納された配列になる)。よってこの場合$linesが配列かどうかは事前には分からないことになります。テキストファイルの1行目を取得するつもりで $lines[0] とやっても、もしテキストファイルが1行だった場合は、その1行の一文字目が返ってきてしまいます。このような事態を防ぐには、

$lines = @(Get-Content file.txt)

のようにすると、$linesには必ず配列が格納されます。たとえテキストファイルが1行でも、要素数1の配列になります。

これとは逆のケースで、「配列か非配列かわからないが、必ず非配列として扱いたい」場合は次のようにするのも手でしょう。

$value = @($unknown)[0]

この場合だともし$unknownが配列だったとしても、その1要素目をとってきて$valueに格納してくれます。本来なら$unknownが配列かどうか確かめて、その要素数がいくつであるかなどを確認すべきなんですが、「非配列か要素数1の配列どちらかが返されるパターン」というのはADSIやXMLなど扱う際に意外と多くて、そういう場合に有用かと思います。

JavaScriptの配列操作と同じようなことをする方法

$array = @(1..5)
# push(配列末尾に値を追加)
$array += 6

# unshift(配列先頭に値を追加)
$array = @(0) + $array

# shift(配列先頭の値を削除)
$array[0]; $array = $array[1..($array.length-1)]

# pop(配列末尾の値を削除)
$array[$array.length-1]; $array=$array[0..($array.length-2)]
元記事:http://blogs.wankuma.com/mutaguchi/archive/2011/03/25/197857.aspx

2010/12/15

TechNetスクリプトセンターは以前はMicrosoftが作成したスクリプト(英文解説付き)がメインコンテンツでしたが、最近はコミュニティベース記事としてユーザーがスクリプトを投稿できるようになっています。今回、Microsoft MVPがこのスクリプトギャラリーに投稿したスクリプトを紹介し、スクリプト作成のTipsを取り上げるコンテンツが公開されました。

MVP が伝授するスクリプト作成のヒントとコツ
http://technet.microsoft.com/ja-jp/scriptcenter/gg486878

私も3つスクリプトを紹介させていただきました。PowerShellやWSHスクリプト作成の参考にぜひどうぞ。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2010/12/15/195802.aspx

2009/01/04

仕事・私事ともにごたごたしてまして大変なのですが、皆様のおかげでなんとか年を越せました。

今年は、いろいろ見えないところも多いんですが、やはりScript系技術を中心に追っていこうと思っています。PowerShell v2を追いたいですし、DLRというかC#4.0/VB10/.NET 4.0がスクリプトに近いものになるのか見届けたいですし。レガシなASPやWSHもまだまだ遊べそうです。本業プログラマ副業ライター趣味スクリプト、という感じでやっていこうかと。あ、ディープな趣味はhttp://winscript.jp/blog/ でw

ここでTips。

Vistaを再インストールしたんですが、OfficeのSelfCert.exeによるコード署名ができなくなってしまいました。その復旧方法。

C:\Users\ユーザー名\AppData\Roaming\Microsoft\Crypto\RSA

を削除。以上。異常になったときも試してみるといいのでは。

だいたいAppDataをバックアップするのが間違いなんですがね。副作用が多いのですが環境がすぐに復帰するので私はバックアップしてます。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2009/01/04/165530.aspx

2007/11/27

True

どっちか要らない子なんじゃ・・・違いがわからないよー

これだけではなんなのでミクシィから適当にコピペ

----------------------------------------------------------------------------------------------------
 
[powershell]new-service何のために
2007年11月26日19:04

あるのかよくわからんー
新しくサービスを登録するっていうんだけど、そういうのってインストーラーの仕事じゃ・・・
おまけにRemove-Serviceコマンドレットがないから作っても削除できないw
sc.exe delete hoge
としないといけない。
sc delete hogeだとSet-Contentのエイリアスが動いちゃうw
なんかすげー危ないコマンドレットな気がするよ。

----------------------------------------------------------------------------------------------------

VistaにはWin32_LogicalMemoryConfigurationないんだ

http://msdn2.microsoft.com/en-us/library/aa394181.aspx
Windows XP and Windows Server 2003: This class is no longer supported. Use the Win32_OperatingSystem class instead.

ほう

 

----------------------------------------------------------------------------------------------------

http://www.anchorsystems.co.jp/anchor/ashp/netmon/faq.html
ファイヤウォールが WMI 呼び出しをブロックしてしまうためです。 Windows 2003 SP1 と Windows XP では、デフォルトでファイヤウォールが ON になっています。ファイヤウォールに WMI 呼び出しを通過させるようにするには、以下のスクリプトを実行してください。
Set objFirewall = CreateObject("HNetCfg.FwMgr")
Set objPolicy = objFirewall.LocalPolicy.CurrentProfile
Set objAdminSettings = objPolicy.RemoteAdminSettings
objAdminSettings.Enabled = TRUE
これで WMI 呼び出しが許可されます。

ファイアウォール嫌いー

リモートでGet-WMIObjectするときにひつよう

----------------------------------------------------------------------------------------------------

[PowerShell]Get-Serviceしょぼすぎ
2007年11月26日00:55

Get-Serviceの戻り値が.NETのSystem.ServiceProcess.ServiceControllerなんすけど、Descriptionプロパティとかないねんな。
でもSet-ServiceでDescriptionを設定できたりする。どうやってちゃんと設定できたかを確認するかはget-wmiobject win32_serviceで調べるらしいwなんだこの中途半端な実装は。
ServiceControllerオブジェクトに対しps1xmlファイルでDescriptionやStartModeをScriptPropertyにして実装しとけよーと思った。せっかく拡張できるんだからさ。

----------------------------------------------------------------------------------------------------
Select-String使えん・・・
2007年11月25日00:10

PS C:\script> select-string "aa" *.ps1
attrib.ps1:7: # Get-Item?R?}???h???b?g??p???AAttributes?v???p?e?B??B
文字化けしとるがな
Shift-JISのファイルも検索・表示できないとはかなり終わってますね
せめて文字コードを指定できるようにしてくれー

UTF8はいけます
 
.NET Frameworkには文字コード判別のクラスとかないのかな・・・
前探してなかった気もする
文字コードを判別する: .NET Tips: C#, VB.NET, Visual Studio
http://dobon.net/vb/dotnet/string/detectcode.html
こういうごり押しが必要なのねー

----------------------------------------------------------------------------------------------------

あと&{スクリプトブロック} は、C#の{}空ブロックと同じことができるらしいー

要するに変数がその中でのみ使われてスコープ抜けたら破棄されるという

これを応用すればtrap文でtry catchみたいなこともできるらしいー

詳しくはPowerShellインアクションを買おう!w

元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/11/27/110583.aspx

2007/05/25

pstファイル(メールや連絡先などが含まれるファイル)が壊れた際、scanpst.exeというツールで修復できますが、その際RSSフィードフォルダがただのフォルダになってしまい壊れてしまいます。それを修復する方法について。

Outlook2007の「ファイル」-「インポートとエクスポート」で、「OPMLファイルからのRSSフィードのインポート」で適当なOPML(*.xml)ファイルをインポートします。じゃんぬさん作成のわんくまブログのOPMLでも使うとよいですね。

それだけで修復できます。Outlook2007を再起動すればアイコンもちゃんとRSSのものに戻ります。すばらしいですね。困った時はぜひどうぞ。

元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/05/25/78352.aspx

2007/05/21

メーリングリスト - Admintech.jp
http://tips.admintech.jp/wiki.cgi?page=%A5%E1%A1%BC%A5%EA%A5%F3%A5%B0%A5%EA%A5%B9%A5%C8

というわけで(ひろえむさん風)、IT Proのためのコミュニティ、Admintech.jpにおいてPowerShellのメーリングリストが開設されました!

私も巡回しますので何かあればぜひどうぞご投稿を。

Technetのほうはマシントラブルでちょっとまだ参加できていないのですが(汗

IT Proの方の、現場でこんなスクリプトが欲しい!という声は貴重です。ぜひみなさんのご意見をお聞かせいただきたいと思っています。私はそれに対するソリューションを提供していけたらなあと考えていますが一緒に試行錯誤していきましょうw

元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/05/21/77485.aspx

次のページへ

Copyright © 2005-2016 Daisuke Mutaguchi All rights reserved

mailto: mutaguchi at roy.hi-ho.ne.jp

Awards

Books

Twitter