PowerShell で Windows サービスを死活監視して自動再起動する(1人運用の常駐サービス対策)
みなさんこんにちは!ヒロポンです!
夜中の3時にスマホが鳴る。「サービスが落ちてます」。起きて VPN 繋いで、再起動 を1個叩いて、また布団に潜る。で、翌朝ちょっと機嫌が悪い。こういうの、ありませんか??
止まると困る常駐サービスを1人で見てる現場だと、これが地味に効いてくるんですよね。
今回は PowerShell サービス 監視 再起動 を自動化する話です。Get-Service で死活を見て、止まってたら Restart-Service で起こして、その事実をログとメールに残す。この一連を、1人運用の目線でコピペで動く形にまとめました。
肝は「無限に再起動を繰り返さない」ところ。ここを外すと、壊れたサービスを延々と叩き続ける別の事故に化けます。そこも含めて設計します。
結論: Get-Service で死活を見て Stopped なら Restart-Service。ただし試行上限は必須
やることを1文にすると、「Get-Service で状態を見て、止まっていたら Restart-Service で起こし、試した事実をログとメールに残す」。これだけです。
タスクスケジューラで5分おきに回せば、簡易的な死活監視のできあがり。
1つだけ絶対に守ってほしいのが、再起動の試行回数に上限を設けることです。「落ちてたら起こす」を無条件で繰り返すと、起動に失敗し続けるサービスを永遠に叩き続けます。上限に達したら諦めて人間に投げる。この切り替えを最初から仕込んでおきます。
なぜ手動監視だと限界なのか
「たまに目視で確認」だと、落ちてる時間帯にたまたま見てなければ気づけません。気づくのは、利用者からのクレームか、夜間のアラートコール。だいたいこの2択です。
かといって監視ツールを新しく入れるのは、1人運用だと稟議も運用負荷も重い。SQL Server Agent みたいな「止まると困るけど、たまにコケる」やつを1個2個見たいだけなのに、大げさすぎるんですよね。
そこで Windows に最初から入っている PowerShell で軽く済ませます。エージェントを常駐させず、タスクスケジューラで定期的にスクリプトを起こすだけ。仕組みがぜんぶ自分の手元のスクリプトに見えているので、後から直すのも楽です。
最短対処: コピペで動く監視スクリプト
順番に組み立てていきます。
まず死活を見る(Get-Service)
状態を取るのは Get-Service です。.Status プロパティが Running なら稼働中、Stopped なら停止中。
$svc = Get-Service -Name 'MSSQLSERVER'
$svc.Status # Running / Stopped / StartPending など
サービス名がうろ覚えのときは、表示名からワイルドカードで探せます。
Get-Service -DisplayName '*SQL*' | Select-Object Name, DisplayName, Status
Get-Service は該当が無いとエラーを吐きます。なので監視スクリプトでは -ErrorAction SilentlyContinue を付けて、後で自前で「見つからなかった」を判定します。こんな感じですね。
停止してたら再起動する(試行上限つき)
ここが本体。まず処理の流れを図にします。

この図をそのままコードに落とします。
# 監視対象と設定
$ServiceName = 'MSSQLSERVER' # 監視したいサービス名
$MaxRetries = 3 # 再起動を試みる上限(無限ループ防止)
$WaitSeconds = 15 # 再起動後に Running を待つ秒数
$LogPath = 'C:\ops\logs\service-watch.log'
function Write-WatchLog {
param([string]$Message)
$line = "{0} {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message
Add-Content -Path $LogPath -Value $line -Encoding UTF8
Write-Host $line
}
function Restart-IfDown {
param([string]$Name, [int]$MaxRetries, [int]$WaitSeconds)
$svc = Get-Service -Name $Name -ErrorAction SilentlyContinue
if ($null -eq $svc) {
Write-WatchLog "サービス $Name が見つかりません"
return $false
}
if ($svc.Status -eq 'Running') {
return $true # 正常。何もしない
}
for ($i = 1; $i -le $MaxRetries; $i++) {
Write-WatchLog "[$i/$MaxRetries] $Name が $($svc.Status)。再起動を試みます"
try {
Restart-Service -Name $Name -Force -ErrorAction Stop
} catch {
Write-WatchLog "再起動失敗: $($_.Exception.Message)"
}
try {
# 停止直後は状態が落ち切らないので、Running になるまで待つ
$svc.WaitForStatus('Running', [TimeSpan]::FromSeconds($WaitSeconds))
} catch [System.ServiceProcess.TimeoutException] {
Write-WatchLog "$WaitSeconds 秒待っても Running になりませんでした"
}
$svc.Refresh()
if ($svc.Status -eq 'Running') {
Write-WatchLog "$Name を再起動しました(試行 $i 回目)"
return $true
}
}
Write-WatchLog "$Name は $MaxRetries 回試しても復帰せず。手動対応が必要です"
return $false
}
$ok = Restart-IfDown -Name $ServiceName -MaxRetries $MaxRetries -WaitSeconds $WaitSeconds
再起動が3回とも失敗したときの動き(サービスが永遠に Stopped を返すケースを再現した実行結果):

試行上限そのものは、for ループの $i -le $MaxRetries。ここがあるおかげで、起動に失敗し続けても3回でループを抜けます。無条件の while ($svc.Status -ne 'Running') で書くと、そこが無限ループの入り口になる。ここは断言しておきます。
再起動をログとメールで残す
上限まで試してもダメだったら、人間に投げます。$ok が $false のときだけメールを送る。
if (-not $ok) {
$mail = @{
To = 'ops@example.co.jp'
From = 'watch@example.co.jp'
Subject = "[警告] $ServiceName が復帰していません"
Body = "$ServiceName の自動再起動に $MaxRetries 回失敗しました。手動確認をお願いします。"
SmtpServer = 'smtp.example.co.jp'
Encoding = ([System.Text.Encoding]::UTF8)
}
Send-MailMessage @mail
}
Send-MailMessage は PowerShell 7 だと「非推奨」扱いです。ただ、業務系の現場でよく使う Windows PowerShell 5.1 では現役で普通に動きます。社内 SMTP に投げるだけならこれで十分。新しめの環境で本格的にやるなら、MailKit などの別ライブラリに寄せる判断もあります。
これで「落ちてたら3回まで自動で起こす、それでもダメなら通知」の一式ができました。いい感じにまとまりましたね!!
ハマりポイント: 知らないと空振りするやつ
素直に書くと踏むやつを、私がやられた順に出しておきます。
① Restart-Service は依存サービスがあると -Force なしで失敗する
Restart-Service を素で叩くと、対象に依存する別のサービスが動いている場合にこう言われます。
# NG: 依存サービスがあると止められない
Restart-Service -Name 'MSSQLSERVER'
# Restart-Service : サービス 'SQL Server (MSSQLSERVER)' には依存するサービスが
# あるため、停止できません。
SQL Server がまさにこれ。SQL Server Agent などが依存側にぶら下がっています。-Force を付けると、依存サービスごと止めて再起動してくれます。
# OK: -Force で依存サービスごと再起動
Restart-Service -Name 'MSSQLSERVER' -Force
ただし -Force は「依存してるやつも一緒に止まる」という意味。何がぶら下がっているかは、事前に Get-Service の DependentServices で確認しておくと安全です。知らないサービスまで巻き込んで止めた、は避けたい。
② 停止直後は状態が Stopped に落ち切っていない
これ、テストだと動いたのに本番でだけ空振りする。なんでや??ってなる、いやなやつでした。Stop-Service や Restart-Service の直後に .Status を見ると、まだ StopPending や StartPending の途中状態のことがあります。
Stop-Service -Name 'MyWorker'
(Get-Service -Name 'MyWorker').Status # まだ StopPending のことがある
停止・起動は非同期で進みます。「もう止まったはず」で次の判定に行くとブレる。WaitForStatus で目的の状態になるまで待ってから判定するのが確実です。
$svc = Get-Service -Name 'MyWorker'
$svc.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(30))
$svc.Refresh()
上のスクリプトで WaitForStatus('Running', ...) を挟んでいるのは、この状態ブレを踏まないためです。ここを省くと「再起動したのに Stopped と誤判定してもう1回叩く」みたいな無駄が起きます。
③ 試行上限を入れ忘れると無限再起動ループになる
冒頭でも書いたやつですが、これが一番こわい。「Running になるまで再起動」を無条件ループで書くと、起動に失敗し続けるサービス(設定ミス・依存先が死んでる・ディスク満杯など)を、スクリプトが延々と叩き続けます。
CPU も食う。ログも無限に膨らむ。SMTP にメールを撒き散らすパターンもある。「何回試したか」を数えて上限で抜ける——これだけは最初から入れておいてください。上のコードの for ($i = 1; $i -le $MaxRetries; $i++) がその役目です。
タスクスケジューラで定期実行する
書いたスクリプトは、Windows タスクスケジューラに登録して5分おきに起こすのが定番です。PowerShell から登録するならこう。
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File C:\ops\watch-service.ps1'
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName 'ServiceWatch' -Action $action -Trigger $trigger `
-User 'SYSTEM' -RunLevel Highest
-User 'SYSTEM' で動かすと、ログオンしていない状態でも実行されます。サービスを操作するので -RunLevel Highest(管理者権限)も付けておきます。こんな感じで、自分が寝ていてもスクリプトが代わりに見張ってくれる状態になります。
現場メモ: 夜間の呼び出しを減らす仕組み化
死活監視をスクリプトに寄せて一番変わったのは、「一次対応の再起動」を人間がやらなくてよくなったことでした。
サービスが一時的にコケるだけなら、スクリプトが3回まで起こしてくれる。本当に人が要るのは「3回試してもダメだった」ときだけになる。夜中に叩き起こされる回数が、体感でだいぶ減りました!!
もう1つ地味に効くのが、ログが残ること。「深夜1時に1回落ちて自動復帰してた」が翌朝わかると、原因調査の起点になります。手で再起動してた頃の「なんか落ちてたらしい」が、記録として積み上がっていく。
やりすぎないコツは、監視対象を絞ること。全サービスを監視すると、たまに落ちても問題ないやつまで再起動して逆に不安定になります。「止まると本当に困る1〜2個」だけに絞る。1人運用だと、結局これがいちばん安定します。
まとめ
PowerShell でのサービス死活監視、要点を振り返ります。
Get-Serviceの.Statusで死活を見て、StoppedならRestart-Serviceで起こすRestart-Serviceは依存サービスがあると-Forceなしで失敗する(SQL Server が典型)- 停止・起動は非同期。直後は状態がブレるので
WaitForStatusで待ってから判定する - 試行回数に上限を設けて無限再起動ループを防ぐ(ここだけは絶対)
- 上限に達したらメール通知に切り替えて人間に投げる
- タスクスケジューラで5分おきに回せば簡易死活監視になる
「落ちたら起こす」を仕組みにしておくと、夜間の呼び出しが確実に減ります。まずは止まると困るサービス1個から、試しに監視をかけてみてください。
動作確認メモ: この記事の Windows サービス操作系 cmdlet(
Get-Service/Restart-Service/Send-MailMessage/ タスクスケジューラ登録)は、構文チェックと「試行上限で無限ループにならない」ループロジックの確認まで実施しています。実際の再起動・依存サービスの連鎖停止・SMTP 送信は Windows 環境(PowerShell 5.1)でお試しください。
よくある質問
Q1. サービスが起動しているか PowerShell で確認するには?
Get-Service -Name サービス名 で取得して .Status を見ます。Running なら稼働中、Stopped なら停止中です。名前がうろ覚えのときは Get-Service -DisplayName '*SQL*' のようにワイルドカードで表示名から探せます。
Q2. Restart-Service で「依存するサービスがあるため停止できません」と出ます
対象サービスに依存する別サービスが動いているためです。Restart-Service -Name サービス名 -Force のように -Force を付けると、依存サービスごと止めて再起動します。何がぶら下がっているかは、事前に (Get-Service -Name サービス名).DependentServices で確認しておくと安全です。
Q3. 再起動した直後に状態を見るとブレるのはなぜ?
停止・起動は非同期で進むので、直後は StopPending / StartPending という途中状態になり、Stopped や Running に落ち切っていないことがあるためです。WaitForStatus で目的の状態になるまで待ってから .Refresh() して判定すると、誤判定を防げます。
Q4. 無限に再起動を繰り返さないようにするには?
再起動の試行回数に上限を設けます。for ($i = 1; $i -le $MaxRetries; $i++) のようにループ回数を数え、上限に達したらループを抜けてメール通知に切り替えます。無条件の while で「Running になるまで」書くと、起動に失敗し続けるサービスを延々と叩き続けるので避けてください。
次に読むべき記事




以上!
執筆者
バイブス父さん — 業務 SE 7 年(正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524(日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


