第2部 ターミナルとシェル
第7章 PowerShell実践 — スクリプトと自動化
第6章で「オブジェクトを流す」考え方とコマンドレットの基礎を身につけました。この章では、それらを組み合わせてスクリプトにまとめ、繰り返しの手作業を自動化します。変数・制御構文・関数といったプログラミングの土台から、ログ整理・CSV処理・サービス監視といった「明日から使える自動化」まで、実際に打てる形で解説します。ここを越えると、あなたの仕事のやり方が変わります。
🎯 この章で学ぶこと
- 変数とデータ型(文字列・数値・配列・ハッシュテーブル)
- 文字列の出力と変数展開、
Write-OutputとWrite-Hostの違い - 制御構文(
if/foreach/for/while)と比較演算子 - 関数の作り方(
param・戻り値)とスクリプトファイル(.ps1)の実行 - 実務で役立つ自動化の型(ログ整理・CSV処理・サービス監視・情報収集)
- タスクスケジューラによる定期実行と、
try/catchによるエラー処理
7.1 変数とデータ型
変数は「値に付けた名札」です。PowerShell の変数はすべて $ で始まります。型(文字列か数値かなど)は代入した値から自動で決まるので、宣言は不要です。
PS C:\> $name = "山田"
PS C:\> $num = 10
PS C:\> $price = 1980.5
PS C:\> $name
山田
PS C:\> $num + 5
15
$num + 5 がきちんと 15 になっているのがポイントです。10 を数値として覚えているので足し算ができます。もし文字列 "10" だったら "105" と連結されてしまいます。「数値は数値、文字列は文字列」として扱われるのは、第6章で見たオブジェクト指向の一貫した姿勢です。
配列 @() — 値を順番に並べて持つ
複数の値をまとめて持つのが配列です。@( ) で囲むか、カンマで並べます。番号(0から始まる)で個々の要素を取り出せます。
PS C:\> $servers = @("web01", "web02", "db01")
PS C:\> $servers[0]
web01
PS C:\> $servers.Count
3
PS C:\> $servers += "db02" # 要素を追加
PS C:\> $servers
web01
web02
db01
db02
ハッシュテーブル @{} — 名前と値のペアで持つ
ハッシュテーブルは「キー(名前)と値」の組を持つ入れ物です。@{ } で作り、キー = 値 をセミコロンか改行で区切ります。Python の辞書に相当します(🐍 Python入門コース第4章)。
PS C:\> $user = @{ Name = "山田"; Dept = "情報システム"; Id = 1024 }
PS C:\> $user["Name"]
山田
PS C:\> $user.Dept
情報システム
PS C:\> $user.Keys
Name
Dept
Id
| データ型 | 書き方 | 用途 |
|---|---|---|
| 文字列 | $s = "文字" | 名前・パスなど |
| 数値 | $n = 10 / $f = 3.14 | 計算・件数 |
| 真偽値 | $true / $false | 条件・フラグ |
| 配列 | @("a", "b", "c") | 順番に並んだ値の集まり |
| ハッシュテーブル | @{ Key = "値" } | 名前で引く値の集まり |
7.2 文字列と出力
結果を画面やファイルに出すための書き方を押さえます。基本の出力は Write-Output ですが、単に変数名を書くだけでも出力されます。
PS C:\> Write-Output "処理を開始します"
処理を開始します
変数展開 — ダブルクォートの中で $ が生きる
PowerShell では、ダブルクォート " " の中の変数は値に置き換わります(変数展開)。一方、シングルクォート ' ' の中は書いたそのままです。この違いは頻出なので必ず押さえてください。
PS C:\> $name = "山田"
PS C:\> Write-Output "こんにちは、$name さん"
こんにちは、山田 さん
PS C:\> Write-Output 'こんにちは、$name さん'
こんにちは、$name さん
プロパティや計算結果を文字列に埋め込みたいときは、サブ式 $( ) を使います。$( ) の中は先に計算され、その結果が文字列に差し込まれます。
PS C:\> $count = (Get-Process).Count
PS C:\> Write-Output "現在 $count 個のプロセスが動いています"
現在 187 個のプロセスが動いています
PS C:\> Write-Output "今日は $(Get-Date -Format 'yyyy/MM/dd') です"
今日は 2026/07/08 です
Write-Output と Write-Host の違い
初心者がつまずくのがこの2つです。見た目はどちらも画面に文字を出しますが、性質がまったく違います。
Write-Output | Write-Host | |
|---|---|---|
| 出すもの | オブジェクト(パイプラインに流れる) | 画面への直接表示のみ |
| パイプで次に渡せるか | 渡せる | 渡せない(その場で消える) |
| 色を付けられるか | 不可 | -ForegroundColor で可 |
| 主な用途 | 後続処理に値を渡す・関数の戻り値 | 人へのメッセージ表示のみ |
PS C:\> Write-Host "完了しました" -ForegroundColor Green
完了しました (緑色で表示される)
Write-Host は画面に表示するだけで、パイプや変数には値が渡りません。「関数の計算結果を次で使いたい」ときに Write-Host を使うと、値が取り出せず混乱します。後続で使う値は Write-Output(または値をそのまま置く)、人への進捗メッセージだけ Write-Host——この使い分けが鉄則です。
7.3 制御構文 — 条件分岐と繰り返し
「もし〜なら」「〜の間ずっと」という流れを作るのが制御構文です。まずは条件判断に使う比較演算子を一覧にします。第6章で触れたとおり、> はリダイレクトに使われるため、比較はハイフン+英字で表します。
| 演算子 | 意味 | 例 | bash / Python の相当 |
|---|---|---|---|
-eq | 等しい | $n -eq 10 | == |
-ne | 等しくない | $n -ne 0 | != |
-gt | より大きい | $n -gt 100 | > |
-ge | 以上 | $n -ge 100 | >= |
-lt | より小さい | $n -lt 10 | < |
-le | 以下 | $n -le 10 | <= |
-like | ワイルドカード一致 | $s -like "*.log" | (部分一致) |
-match | 正規表現に一致 | $s -match "ERROR" | =~ |
if / elseif / else
条件によって処理を分けます。条件は ( ) に、実行する内容は { } に書きます。
# ディスク使用率で警告を出し分ける
$usage = 82
if ($usage -ge 90) {
Write-Host "危険: ディスク使用率 $usage%" -ForegroundColor Red
} elseif ($usage -ge 80) {
Write-Host "警告: ディスク使用率 $usage%" -ForegroundColor Yellow
} else {
Write-Host "正常: ディスク使用率 $usage%" -ForegroundColor Green
}
Python では字下げ(インデント)で範囲を表しましたが、PowerShell は { } で範囲を表します。この「波かっこでブロックを囲む」書き方は、bash や C言語系と同じ発想です。
foreach — 集まりを1つずつ処理する
配列やコマンドの結果を、1個ずつ取り出して処理するのが foreach です。これが自動化の主役になります。
# サーバー名のリストを1つずつ処理する
$servers = @("web01", "web02", "db01")
foreach ($server in $servers) {
Write-Host "$server に接続を試みます..."
}
web01 に接続を試みます...
web02 に接続を試みます...
db01 に接続を試みます...
パイプラインの中で1件ずつ処理する ForEach-Object(第6章の $_ を使う版)もあります。Get-Process | ForEach-Object { $_.Name } のように、流れてくるオブジェクトをその場で加工したいときに使います。foreach 文は「手元の配列を回す」、ForEach-Object は「パイプで流れてくるものを回す」と覚えると整理できます。
for と while
回数が決まっている繰り返しは for、条件が成り立つ間ずっと繰り返すのは while です。
# for: 1 から 3 まで
for ($i = 1; $i -le 3; $i++) {
Write-Host "$i 回目の試行"
}
# while: 条件が真である間くり返す
$count = 3
while ($count -gt 0) {
Write-Host "残り $count"
$count--
}
7.4 関数 — 処理に名前をつけて再利用する
同じ処理を何度も書くのは無駄ですし、間違いのもとです。処理をひとまとめにして名前をつけたものが関数です。function の後ろに名前(動詞-名詞にするのが作法)、受け取る値は param( ) に書きます。
function Get-DiskStatus {
param($Usage)
if ($Usage -ge 90) {
return "危険"
} elseif ($Usage -ge 80) {
return "警告"
} else {
return "正常"
}
}
# 呼び出し方(引数は空白区切り、または名前つき)
Get-DiskStatus 85
Get-DiskStatus -Usage 95
警告
危険
param($Usage) で「$Usage という値を1つ受け取る」と宣言し、return で結果を返します。呼び出すときは Get-DiskStatus 85 のように空白で区切って渡すか、-Usage 95 のように名前を明示します。コマンドレットと同じ呼び出し方ができるのが PowerShell の関数の特徴です。
PowerShell の関数は、ブロック内で「出力されたものはすべて戻り値になる」という独特の仕様です。return は「そこで関数を抜ける」意味が主で、値を返すこと自体は return なしでも起こります。そのため、関数の途中で Write-Output したり、うっかり変数名だけ書いたりすると、それも戻り値に混ざります。「関数の戻り値に余計なものが混ざる」ときは、途中に出力を生む行がないか確認しましょう。人への表示は前述の Write-Host を使えば戻り値には混ざりません。
7.5 スクリプトファイル(.ps1)
ここまでの処理をファイルに保存したものがスクリプトで、拡張子は .ps1 です。メモ帳や VS Code で書けます。# から行末まではコメント(実行されないメモ)です。
# disk-check.ps1 --- ディスク使用状況をチェックするサンプル
# 作成: 情報システム部 更新: 2026-07-08
$drive = Get-PSDrive C
$usedGB = [math]::Round($drive.Used / 1GB, 1)
$freeGB = [math]::Round($drive.Free / 1GB, 1)
Write-Host "C ドライブ: 使用 $usedGB GB / 空き $freeGB GB"
if ($freeGB -lt 10) {
Write-Host "空き容量が少なくなっています" -ForegroundColor Yellow
}
実行するには、そのフォルダに移動して .\ファイル名 と打ちます。先頭の .\ は「今いるフォルダにあるこのファイル」という意味で、これを省くと PowerShell は実行してくれません(意図しないスクリプトの誤実行を防ぐ安全策です)。
PS C:\> cd C:\Scripts
PS C:\Scripts> .\disk-check.ps1
C ドライブ: 使用 210.4 GB / 空き 45.8 GB
「スクリプトの実行が無効になっている」と拒否されたら、第6章の実行ポリシーを思い出してください。Get-ExecutionPolicy で確認し、Set-ExecutionPolicy RemoteSigned(またはユーザー限定で -Scope CurrentUser)に設定すれば、自分で書いたローカルの .ps1 を実行できるようになります。ネットから落としたスクリプトは、中身を必ず読んでから動かしましょう。
7.6 実務で役立つ自動化の型
いよいよ本番です。情シスの現場で頻出する4つの自動化パターンを、そのまま応用できる形で紹介します。第6章のコマンドレットと、この章の制御構文・変数がすべてここで合流します。
例1 — 古いログファイルを一覧・削除する
「30日より前のログファイルを掃除したい」。Get-ChildItem でファイルを集め、Where-Object で更新日時(LastWriteTime)を条件に絞り込みます。基準日は Get-Date から AddDays(-30) で「30日前」を作ります。
# 30日より古い .log ファイルを一覧する
$limit = (Get-Date).AddDays(-30)
Get-ChildItem C:\Logs -Filter *.log |
Where-Object { $_.LastWriteTime -lt $limit } |
Select-Object Name, LastWriteTime
まず「一覧」で対象を目で確認するのが安全です。問題なければ、末尾を Remove-Item に変えて削除します。ただしいきなり削除は禁物。次のように -WhatIf を付けて「何が消えるか」だけを先に確認します。
PS C:\> Get-ChildItem C:\Logs -Filter *.log |
>> Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
>> Remove-Item -WhatIf
What if: 対象 "C:\Logs\app-20260501.log" に対して操作 "ファイルの削除" を実行します。
What if: 対象 "C:\Logs\app-20260510.log" に対して操作 "ファイルの削除" を実行します。
表示された対象が正しければ、-WhatIf を外して本当に削除します。-WhatIf は破壊的操作の前の「予行演習」であり、身を守る最重要オプションです(詳しくは章末のセキュリティ・運用の視点で)。
例2 — CSV を読んで処理し、結果を書き出す
PowerShell は CSV を「表」として理解します。Import-Csv で読み込むと、1行=1オブジェクト、列名=プロパティになります。だから列名で値を扱えます。次の users.csv を例にします。
Name,Dept,Status
山田,情報システム,active
佐藤,営業,inactive
鈴木,情報システム,active
# CSV を読み込み、有効なユーザーだけ抽出して別ファイルに保存
Import-Csv C:\data\users.csv |
Where-Object { $_.Status -eq "active" } |
Select-Object Name, Dept |
Export-Csv C:\data\active-users.csv -NoTypeInformation -Encoding UTF8
Import-Csv で読み、Where-Object { $_.Status -eq "active" } で有効なユーザーだけを絞り、Export-Csv で新しい CSV に書き出しています。-NoTypeInformation は余計な型情報の行を出さないため、-Encoding UTF8 は日本語の文字化けを防ぐためのおまじないです(文字コードは第2章)。Excel を手作業で開いてフィルタする代わりに、この数行で確実に、何度でも同じ処理ができます。
例3 — サービスを監視し、止まっていたら起動する
「重要なサービスが停止していたら自動で立ち上げ直す」。運用の定番です。Get-Service で状態を取り、Status が Running でなければ Start-Service します。
# 監視したいサービス名を配列で持つ
$targets = @("Spooler", "W32Time", "Dnscache")
foreach ($name in $targets) {
$svc = Get-Service -Name $name
if ($svc.Status -ne "Running") {
Write-Host "$name が停止しています。起動します..." -ForegroundColor Yellow
Start-Service -Name $name
} else {
Write-Host "$name は正常に稼働中です" -ForegroundColor Green
}
}
Spooler は正常に稼働中です
W32Time が停止しています。起動します...
Dnscache は正常に稼働中です
配列 $targets に監視対象を並べ、foreach で1つずつ状態を確認する——7.1・7.3・7.5 で学んだ要素がすべて組み合わさっています。監視対象を増やしたいときは、配列に名前を足すだけです。
例4 — プロセス情報を集めてレポートに残す
「今この瞬間のメモリ使用トップ10を、日付つきのファイルに記録しておきたい」。第6章のパイプラインの集大成です。ファイル名に日付を埋め込めば、毎日の記録が上書きされずに残ります。
# メモリ使用量トップ10を CSV に保存(ファイル名に当日日付)
$date = Get-Date -Format "yyyyMMdd"
$out = "C:\Reports\top-memory-$date.csv"
Get-Process |
Sort-Object WS -Descending |
Select-Object -First 10 Name, Id, @{Name="MemoryMB"; Expression={ [math]::Round($_.WS / 1MB, 1) }} |
Export-Csv $out -NoTypeInformation -Encoding UTF8
Write-Host "レポートを $out に保存しました" -ForegroundColor Green
@{Name="MemoryMB"; Expression={...}} は「計算プロパティ」と呼ばれる書き方で、既存のプロパティ(WS=バイト単位)を MB に直した新しい列 MemoryMB を作っています。ハッシュテーブル(7.1)がこんなところでも活躍します。イベントログを対象にしたいなら、Windows PowerShell 5.1 では Get-EventLog -LogName System -Newest 50、PowerShell 7 では Get-WinEvent を使い、同じように Export-Csv で保存できます。
7.7 タスクスケジューラで定期実行する
せっかく作った自動化も、毎回手で実行していては半分しか報われません。Windows タスクスケジューラに登録すれば、「毎朝7時」「1時間ごと」といった定期実行を無人でこなせます。考え方はシンプルで、「いつ(トリガー)」「何を(アクション=PowerShell でスクリプトを起動)」を登録するだけです。
アクションのプログラムには powershell.exe(または PowerShell 7 なら pwsh.exe)を指定し、引数でスクリプトのパスを渡します。GUI のタスクスケジューラでも設定できますが、PowerShell 自身で登録することもできます。
# 毎朝 7:00 に disk-check.ps1 を実行するタスクを登録する例
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\disk-check.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 7:00am
Register-ScheduledTask -TaskName "DailyDiskCheck" `
-Action $action -Trigger $trigger -Description "毎朝のディスク点検"
行末の `(バッククォート)は「1行が長いので次の行へ続く」という継続記号です。-Daily -At 7:00am がトリガー(毎日7時)、-Argument がアクション(このスクリプトを実行せよ)です。登録済みのタスクは Get-ScheduledTask で確認できます。
無人で動くスクリプトは、あなたが見ていないところで失敗するかもしれません。だからこそ、実行結果を必ずファイルに記録(ログ出力)する設計にしておきましょう。例4のように結果を Export-Csv や Out-File でファイルに残し、後から「ちゃんと動いていたか」を確認できるようにするのが運用の基本です(ログの考え方は第14章で詳しく扱います)。
7.8 エラー処理 — try / catch と -ErrorAction
自動化スクリプトは「途中で何か失敗しても、暴走せず・黙って止まらず、きちんと後始末する」ことが大切です。そのための仕組みが try / catch です。try { } の中でエラーが起きると、処理は catch { } に飛び、$_ にエラー内容が入ります。
try {
$svc = Get-Service -Name "存在しないサービス" -ErrorAction Stop
Start-Service -Name $svc.Name
Write-Host "サービスを起動しました" -ForegroundColor Green
}
catch {
Write-Host "エラーが発生しました: $($_.Exception.Message)" -ForegroundColor Red
}
finally {
Write-Host "処理を終了します"
}
エラーが発生しました: サービス '存在しないサービス' が見つかりません。
処理を終了します
ここで重要なのが -ErrorAction Stop です。PowerShell の多くのコマンドレットは、エラーが起きても赤いメッセージを出してそのまま次に進んでしまうことがあり、それだと catch が反応しません。-ErrorAction Stop(エラーが出たら止まれ)を付けることで、確実に catch へ処理を渡せます。finally { } は、成功しても失敗しても必ず最後に実行される後始末用のブロックです。
| -ErrorAction の値 | エラー時の動き |
|---|---|
Continue(既定) | メッセージを出して処理を続行 |
Stop | 処理を止める(try/catchで捕まえられる) |
SilentlyContinue | メッセージを出さず続行(握りつぶす) |
Inquire | どうするか利用者に尋ねる |
Python の try/except(🐍 Python入門コース第6章)を学んだ人には、発想はそのままです。「失敗するかもしれない処理を try で囲み、失敗時の対応を catch に書く」——自動化を本番に載せるなら、この一手間が信頼性を決めます。
PowerShell の自動化は、限られた人数で多くの端末やサーバーを見る「一人情シス」(🧑💻 一人情シスコース)にとって最大の武器です。手作業を減らせば、ミスも減り、時間も生まれます。しかし、力が大きいぶん暴走したときの被害も大きい——ここを絶対に忘れてはいけません。運用の鉄則を3つ挙げます。(1) 破壊的操作は必ず -WhatIf で予行演習する。削除(Remove-Item)・停止(Stop-Service)・上書きは、まず -WhatIf で「何が起きるか」を確認してから、外して本実行する。ループの中の Remove-Item は、条件を1つ間違えるだけで数百ファイルを一瞬で消します。(2) いきなり本番で回さない。まずテスト用のフォルダや検証機で試し、少数で動作を確かめてから対象を広げる。(3) スクリプトは記録を残し、権限は最小に。結果をログに残せば、無人実行でも異常に気づけます。そして第6章の実行ポリシーと最小権限の原則のとおり、スクリプトを常に管理者権限で回すのは避け、必要な操作にだけ権限を与えます。「自動化で楽をする」ことと「安全装置を外す」ことは別物です。-WhatIf という一手間を惜しまない人が、結局いちばん速く、いちばん信頼される運用者になります。
まとめ
- 変数は
$で始まり型は自動判定。配列@()は順番の集まり、ハッシュテーブル@{}は名前で引く集まり - ダブルクォート内は変数展開される。計算結果の埋め込みは
$( )。値を後続に渡すならWrite-Output、人への表示だけならWrite-Host - 比較は
-eq-ne-gt-lt-like。分岐はif、繰り返しはforeach/for/while - 関数は
function 名前 { param(...) ... }。スクリプトは.ps1、実行は.\ファイル名(実行ポリシーに注意) - 実務の型:古いログの整理、CSV の
Import-Csv/Export-Csv、サービス監視と自動起動、情報収集レポート - 定期実行はタスクスケジューラ。エラー処理は
try/catchと-ErrorAction Stop。破壊的操作は必ず-WhatIfで先に確認する
練習問題
次の2つのコマンドの出力はどう違うか説明してください。また、変数 $dept の値をそのまま文字どおり $dept と表示したい(展開させたくない)場合は、どちらのクォートを使えばよいですか。
PS C:\> $dept = "情報システム"
PS C:\> Write-Output "所属: $dept"
PS C:\> Write-Output '所属: $dept'
解答を見る
ダブルクォート "所属: $dept" は変数が展開され 所属: 情報システム と表示されます。シングルクォート '所属: $dept' は書いたそのままで 所属: $dept と表示されます。したがって、$dept という文字列をそのまま表示したいときはシングルクォートを使います。「変数を値に置き換えたいならダブル、文字どおり出したいならシングル」と覚えます。
フォルダ C:\Backup にある .bak ファイルのうち、更新日時が「90日より前」のものを削除したいと考えています。ただし、本当に削除する前に、何が消えるかを確認したいです。(1) 確認用のコマンドを1つ書いてください。(2) なぜこの確認ステップが重要か、運用の観点で述べてください。
解答を見る
PS C:\> Get-ChildItem C:\Backup -Filter *.bak |
>> Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-90) } |
>> Remove-Item -WhatIf
(1) 末尾に -WhatIf を付けると、実際には削除せず「どのファイルが削除対象になるか」だけが表示されます。表示内容が正しいと確認できたら、-WhatIf を外して本実行します。
(2) 削除は取り消せない破壊的操作で、Where-Object の条件を1つ間違えるだけで必要なファイルまで一瞬で消してしまう危険があります。-WhatIf による予行演習は、その事故を未然に防ぐ最も手軽で効果的な安全装置です。「いきなり本番で回さない」の実践そのものです。
サービス名の配列 $targets = @("Spooler", "Dnscache") について、各サービスが停止(Running でない)していたら起動し、稼働中ならその旨を表示するスクリプトを、foreach と if を使って書いてください。状態は Get-Service の Status プロパティで判定します。
解答を見る
$targets = @("Spooler", "Dnscache")
foreach ($name in $targets) {
$svc = Get-Service -Name $name
if ($svc.Status -ne "Running") {
Write-Host "$name が停止しています。起動します..." -ForegroundColor Yellow
Start-Service -Name $name
} else {
Write-Host "$name は稼働中です" -ForegroundColor Green
}
}
foreach で配列を1つずつ取り出し、Get-Service で状態を取得。$svc.Status -ne "Running"(Running でない)が真なら Start-Service で起動します。監視対象を増やしたいときは、配列 $targets にサービス名を追加するだけで済みます。実運用では、起動に失敗する場合に備えて try/catch と -ErrorAction Stop を組み合わせると、より堅牢になります。