Home > Windows にまつわる e.t.c.

リモート コンピューターのパラレル バッチ操作(Invoke-Command -AsJob)


リモートコンピューター台数が少ない場合は、Invoke-Command でシーケンシャル処理でも構わないのですが、処理対象のリモートコンピューターが100台以上あるとか、1台当たりの処理が長時間に及ぶ場合は時間短縮のためにパラレルに処理を実行したくなります。

このパラレル処理をするのが Invoke-Command の -AsJob オプションです。

まずは試しに dir c:\ を -AsJob で投入してみましょう。

PS C:\> $Return = Invoke-Command log -ScriptBlock { dir c:\ } -AsJob
PS C:\> echo $Return

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
33     Job33           RemoteJob       Running       True            TergetServer          dir c:\

Invoke-Command は即座に終了し、戻り値にジョブの実行状態が格納されます。

実行状態は Get-Job で確認できます。

PS C:\> Get-Job

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
33     Job33           RemoteJob       Completed     True            TergetServer          dir c:\

 

処理状態を表す State には以下の種類があります(これ以外にあるかも)

Running 実行中
Completed 正常完了
Failed 異常終了
Disconnected 切断された

余談ですが、Invoke-Command -AsJob でリモートコンピューターを再起動すると、再起動時に State が Completed になります。

Completedしたジョブの処理結果は Receive-Job で受け取ります。(Invoke-Command そのものと、戻り値ハンドリングはこちらをどうぞ。-AsJob はオプションなので、そのまま応用できます)

PS C:\> $Return = Receive-Job -Id 33
PS C:\> echo $Return


    ディレクトリ: C:\


Mode                LastWriteTime     Length Name                                  PSComputerName
----                -------------     ------ ----                                  --------------
da---        2014/01/25     14:33            CheckEventLog                         TergetServer
d----        2013/09/12     16:17            inetpub                               TergetServer
d----        2014/12/01     12:08            LogMove                               TergetServer
d----        2013/08/23      0:52            PerfLogs                              TergetServer
d----        2014/12/01     12:14            ping_log                              TergetServer
d-r--        2015/02/15     10:23            Program Files                         TergetServer
d----        2015/02/15     10:23            Program Files (x86)                   TergetServer
d----        2014/12/01     12:10            pSyslog                               TergetServer
d----        2014/10/10     10:21            Tools                                 TergetServer
d-r--        2013/09/12     16:23            Users                                 TergetServer
d----        2015/03/12      4:37            Windows                               TergetServer
d----        2013/09/12     16:15            Windows.old                           TergetServer
d----        2015/03/15     12:32            WindowsUpdate                         TergetServer
d----        2014/12/01     12:04            work                                  TergetServer
d----        2015/03/15     12:25            WU_Log                                TergetServer

Receive-Job で結果を取り出せるのは1度きりなので、もう一度 Get-Job すると、HasMoreData が False に変わり、戻りデーターがもう無いことがわかります。

PS C:\> Get-Job

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
33     Job33           RemoteJob       Completed     False           TergetServer          dir c:\

処理が終わっても、ジョブは残っているので Remove-Job でジョブを削除します。

Remove-Job -Id 33

実行中のジョブを強制終了するには Stop-Job コマンドレットを使います

Stop-Job -Id 33

 

Job を Failed にする

-AsJob で投入したジョブは、return で成否等の戻り値を返して制御をする事が多いと思いますが、致命的なエラーが発生した場合は強制的に Job を Failed にすることが出来ます。

Job を Failed する場合は、例外を throw します。

例外発生時のデフォルト動作は「処理継続」なので、異常終了させるために $ErrorActionPreference に "Stop" をセットして処理停止にします。

# 例外発生時動作終了
$ErrorActionPreference = "Stop"

$Message = "○○異常"
throw $Message

例外発生時の動作をデフォルトに戻すには、明示的に $ErrorActionPreference に "Continue" をセットする必要があります。

 

実行状態監視例

こんな感じでループまわして状態監視することが出来ます。

# -AsJob 投入した ジョブの状態確認
do{
    # ジョブ取得
    $Jobs = Get-Job

    # 実行中ジョブ
    $RunningJobs = $Jobs | ?{ $_.State -eq "Running"}

    # 正常終了したジョブ
    $CompletedJobs = $Jobs | ?{ $_.State -eq "Completed"}
    
    if( $CompletedJobs -ne $null ){
        foreach( $Job in $CompletedJobs ){
            $Location = $Job.Location

            # 戻り値取得
            $Echos = Receive-Job -Id $Job.Id
            $Return = $Echos[$Echos.Lenght - 1]
            
            # エラー(戻り値に $true/$false を返す仕様の場合)の場合は標準出力を echo
            if( $Return -ne $true ){
                echo "[ERROR] $Location Error"
                echo "[ERROR] ------------ $Location stdout echo Start ------------"
                foreach( $Echo in $Echos ){
                    echo $Echo
                }
                echo "[ERROR] ------------ $Location stdout echo End ------------"
            }
            else{
                echo "[INFO] $Location Completed"
            }
            
            # Job 削除
            Remove-Job -Id $Job.Id
        }
    }

    # 残っているジョブ状態を表示(10秒間隔)
    $AllJobs = get-job
    if( $AllJobs -ne $null ){
        $Now = Get-Date
        $Message = "未処理ジョブ " + $Now
        echo $Message
        $AllJobs | Format-Table -Property Id,Name,State,Location -AutoSize | Out-Host
        echo ""
        echo ""
        echo ""
        sleep 10
    }
}
while( $RunningJobs -ne $null )

# 異常終了したジョブ
$FailedJobs = Get-Job | ?{ $_.State -eq "Failed"}
if( $FailedJobs -ne $null ){
    # 一覧表示
    echo "Fail Job"
    $FailedJobs | Format-Table -Property Id,Name,State,Location -AutoSize | Out-Host
    echo ""

    # 異常終了した Job を処理
    foreach( $Job in $FailedJobs ){
        $Location = $Job.Location

        # 標準出力と戻り値を echo して Job 削除
        echo "[FAIL] $Location Failed"
        echo "[FAIL] ------------ $Location stdout echo Start ------------"
        $Failechos = Receive-Job -Id $Job.Id
        if( $Failechos -ne $null ){
            foreach( $Failecho in $Failechos ){
                echo $Failecho
            }
        }
        echo "[FAIL] ------------ $Location stdout echo End ------------"
        Remove-Job -Id $Job.Id
    }
}

# 切断されたジョブ(ジョブ強制削除)
$DisconnectedJobs = Get-Job | ?{ $_.State -eq "Disconnected"}
if( $DisconnectedJobs -ne $null ){
    # 一覧表示
    echo "Disconnect Job"
    $DisconnectedJobs | Format-Table -Property Id,Name,State,Location -AutoSize | Out-Host
    echo ""

    # Job 強制削除
    foreach( $Job in $DisconnectedJobs ){
        Stop-Job -Id $Job.Id
        Remove-Job -Id $Job.Id
    }
}

 

 

Invoke-Command -AsJob の注意点は、投入元の PowerShell プロンプトを閉じたり、コンピューターの再起動してしまうと、セッションが切断され Get-Job でジョブを捕まえる事が出来なくなる点です。

この点を注意すれば、Invoke-Command -AsJob はパラレルに大量のジョブを投入でき、ループで回して戻り値で細かく制御することも可能なのでお勧めです。

 

 

# 同一カテゴリー
リモート コンピューターの対話操作(Enter-PSSession)
リモート コンピューターのバッチ操作(Invoke-Command)

 

back.gif (1980 バイト)

home.gif (1907 バイト)

Copyright © MURA All rights reserved.