【PowerShell】複数サーバーへSSH接続を一括実行する方法|スクリプト例付き

【PowerShell】複数サーバーへSSH接続を一括実行する方法|スクリプト例付き PowerShell

複数のサーバーに同じコマンドを流したい、設定を一斉に確認したい――そんなときは PowerShell から OpenSSH を呼び出すだけで、安全かつ再現性のあるバッチ実行が実現できます。
ここでは鍵認証を前提に、最小の直列実行から高速な並列実行、ジョブ管理、ログ保存、エラー時の切り分けまで、実務でそのまま使えるスクリプト例を順に示します。

前提と準備

Windows 10 以降で OpenSSH クライアントが導入済みであること、PowerShell 7 以上を推奨すること、接続先に公開鍵を登録済みであることを前提にします。
短い別名で接続できるよう ~/.ssh/config に Host ブロックを用意しておくと保守が容易になります。パスフレーズ付き鍵を使う場合は ssh-agent を自動起動し、事前に ssh-add で登録しておきます。

サーバーリストの用意

接続先はテキストや CSV にまとめておくと差し替えが簡単です。行ごとにエイリアスまたは user@host を記載した servers.txt を例に進めます。

# 例: servers.txt
web-01
web-02
db-01
jump@example.com -p 2222

直列実行の最小スクリプト

まずは一台ずつ順番にコマンドを実行します。バッチ用途では無対話で失敗を検知できるよう、BatchMode と厳格なホスト鍵設定を有効にします。

$servers = Get-Content -Path "$HOME\servers.txt"
$cmd     = "uname -a"     # 実行したいコマンドを差し替え

foreach ($s in $servers) {
  Write-Host "==> $s" -ForegroundColor Cyan
  $psi = @{
    FilePath = "ssh"
    ArgumentList = @(
      "-o","BatchMode=yes",
      "-o","StrictHostKeyChecking=accept-new",
      $s,$cmd
    )
    NoNewWindow = $true
    Wait = $true
    PassThru = $true
  }
  $p = Start-Process @psi
  if ($p.ExitCode -ne 0) {
    Write-Warning "FAILED: $s (exit $($p.ExitCode))"
  }
}

並列実行(PowerShell 7)

台数が多い場合は ForEach-Object -Parallel で同時実行します。回線や先方負荷を考慮してスロットルを調整します。

$servers = Get-Content "$HOME\servers.txt"
$cmd     = "df -h"

$results = $servers | ForEach-Object -Parallel {
  $s = $_
  $psi = @{
    FilePath = "ssh"
    ArgumentList = @("-o","BatchMode=yes","-o","StrictHostKeyChecking=accept-new",$s,$using:cmd)
    NoNewWindow = $true; Wait = $true; PassThru = $true
  }
  $p = Start-Process @psi
  [pscustomobject]@{
    Server  = $s
    Exit    = $p.ExitCode
    Stdout  = $p.StandardOutput.ReadToEnd()
    Stderr  = $p.StandardError.ReadToEnd()
  }
} -ThrottleLimit 8

$results | Sort-Object Server | ForEach-Object {
  if ($_.Exit -eq 0) { Write-Host "==> $($_.Server)`n$($_.Stdout)" -ForegroundColor Green }
  else { Write-Host "==> $($_.Server) [FAILED]" -ForegroundColor Red; Write-Host $_.Stderr }
}

PowerShell 5.x でも使えるジョブ実行

旧環境では Start-JobReceive-Job で擬似並列化します。完了まで待機し、結果を収集して集約します。

$servers = Get-Content "$HOME\servers.txt"
$cmd     = "uptime"
$jobs = foreach ($s in $servers) {
  Start-Job -ScriptBlock {
    param($server,$command)
    $psi = New-Object System.Diagnostics.ProcessStartInfo "ssh", "-o BatchMode=yes -o StrictHostKeyChecking=accept-new $server $command"
    $psi.RedirectStandardOutput = $true; $psi.RedirectStandardError = $true; $psi.UseShellExecute = $false
    $p = [System.Diagnostics.Process]::Start($psi); $p.WaitForExit()
    [pscustomobject]@{ Server=$server; Exit=$p.ExitCode; Out=$p.StandardOutput.ReadToEnd(); Err=$p.StandardError.ReadToEnd() }
  } -ArgumentList $s,$cmd
}
Wait-Job $jobs | Out-Null
$results = $jobs | Receive-Job
Remove-Job $jobs
$results | Format-Table -AutoSize

権限昇格や環境差の吸収

コマンドに sudo が必要な場合は事前に NOPASSWD を許可するか、対話不要なサブコマンドに限定します。環境変数や PATH 差で失敗する場合は、フルパスでコマンドを指定するか、/usr/bin/env を併用します。

$cmd = "/usr/bin/env bash -lc 'sudo -n systemctl is-active nginx'"

実行ログの永続化

監査や後追いのため、結果をファイルに保存します。成功・失敗を行単位で明示し、タイムスタンプを含めると便利です。

$ts = (Get-Date).ToString("yyyyMMdd-HHmmss")
$log = "$PWD\ssh-batch-$ts.log"
$results | ForEach-Object {
  $status = if ($_.Exit -eq 0) { "OK" } else { "NG" }
  "@@ $($status) $($_.Server)`n$($_.Stdout)$($_.Stderr)`n" | Out-File -FilePath $log -Append -Encoding utf8
}
Write-Host "Saved: $log"

ファイル配布や回収の自動化

設定配布は scp を併用すると簡潔です。成功検証用にハッシュ確認のコマンドを続けて実行すると堅牢になります。

$servers = Get-Content "$HOME\servers.txt"
$local   = "C:\work\app.conf"
$remote  = "/etc/app/app.conf"

foreach ($s in $servers) {
  & scp -o BatchMode=yes $local "$s:$remote"
  & ssh -o BatchMode=yes $s "sha256sum $remote"
}

タイムアウトと再試行の実装

一部のホストで接続が遅延する場合は ConnectTimeout を指定し、失敗時に指数バックオフで再試行します。

function Invoke-SshWithRetry {
  param([string]$Server,[string]$Command,[int]$MaxRetry=3)
  for ($i=1; $i -le $MaxRetry; $i++) {
    $exit = & ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new $Server $Command; $rc = $LASTEXITCODE
    if ($rc -eq 0) { return 0 }
    Start-Sleep -Seconds ([math]::Pow(2,$i))
  }
  return 1
}

よくある失敗の切り分け

Permission denied が出る場合は対象ユーザーの ~/.ssh/authorized_keys とパーミッションを再確認します。ホスト鍵が変わったと表示される場合は中間者攻撃を疑い、問題がないと判断できるときだけ known_hosts の該当行を削除し再登録します。
コマンドは単独の ssh で先に検証し、バッチ投入前に成功条件と終了コードを必ず確認します。

まとめ

PowerShell から OpenSSH を呼び出すだけで、直列・並列・ジョブ・ログ・再試行といった運用必須の要素を短いスクリプトで組み上げられます。
少数台なら直列、台数が多ければ並列、古い環境ではジョブ方式と使い分け、鍵認証と BatchMode、厳格なホスト鍵検証を基本に据えることで、安全な一括実行基盤が完成します。