株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。
Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
▶コーポレートサイト
本記事は 「PowerShell 業務自動化レシピ」 シリーズの第12回、最終回です。このシリーズは、文法解説ではなく 「1記事=1つの業務課題」を、コピペで動く検証済みスクリプトで解決することを目的とした実務レシピ集でした(全12回)。SES・受託・社内運用の現場で、いまだに手作業でやっている地味な作業を、片っ端から自動化してきました。
第1回 の一括リネームから、第11回 の「壊れにくいスクリプトの作法」まで、各回で「ここでハマる」という注意点を散発的に挟んできました。最終回となる第12回は、その横断的な総まとめです。テーマは 「現場でハマる罠10選」。業務自動化で実際に踏みやすい落とし穴を10個、すべて 「症状 → 原因 → 対策コード」 の形で並べ直します。
本記事も基準は PowerShell 7(pwsh、クロスプラットフォーム) です。Windows標準の Windows PowerShell 5.1 での差異は、罠ごとに明記します。今回は「読んで確認する」性質の記事なので、破壊的な操作は基本的にありません(罠9のファイル削除例のみ
-WhatIf前提です)。
各罠の最後に 「一言まとめ」 を付けました。時間が無いときは、その太字部分だけ拾い読みしても効きます。
罠1:文字コード ―― 「Export-Csvしたらメモ帳で開いて文字化け」「Excelで文字化け」
症状
Export-Csv や Out-File で出した日本語CSV/テキストが、環境によって文字化けする。PowerShell 7 で出したファイルを Windows PowerShell 5.1 のスクリプトや古いツールで読むと化ける。逆も起きる。
原因
既定の文字コードが PowerShell 7 と 5.1 で違うのが根本原因です。
| コマンド | PowerShell 7(pwsh)の既定 | Windows PowerShell 5.1 の既定 |
|---|---|---|
Export-Csv / Out-File / Set-Content
|
UTF-8(BOMなし) | 環境の既定(多くはShift_JIS/ANSI) |
Get-Content(読み込み) |
UTF-8 とみなす | 環境の既定とみなす |
さらにExcelは、BOMの無いUTF-8 CSVをダブルクリックで開くとShift_JISと誤判定して文字化けすることがあります(第3回で扱った通り)。
対策コード
Excelで開く前提のCSVは、PowerShell 7 では -Encoding UTF8BOM を明示します。
# ❌ 既定任せ:7はBOMなしUTF-8 → Excelで文字化けすることがある
$data | Export-Csv -LiteralPath "C:\out\report.csv" -NoTypeInformation
# ✅ Excelで開くなら BOM付きUTF-8 を明示
$data | Export-Csv -LiteralPath "C:\out\report.csv" -Encoding UTF8BOM -NoTypeInformation
Get-Content / Set-Content も同様に、化けて困るなら読みも書きもエンコードを明示するのが鉄則です。
# Shift_JIS の古いファイルを読むとき(PowerShell 7)
$lines = Get-Content -LiteralPath "C:\in\legacy.txt" -Encoding Default # OS既定コードページ(日本語環境ならShift_JIS系)
# UTF-8(BOM付き)で書き戻す
Set-Content -LiteralPath "C:\out\fixed.txt" -Value $lines -Encoding UTF8BOM
UTF8BOMという指定値は PowerShell 7 以降専用です。5.1 には存在しません。5.1 でBOM付きUTF-8を出したいときは、[System.IO.File]::WriteAllLines+UTF8Encoding($true)を使います(第3・4回で紹介した手法)。
# 【5.1向け】BOM付きUTF-8で書き出す
$csv = $data | ConvertTo-Csv -NoTypeInformation
$utf8Bom = New-Object System.Text.UTF8Encoding($true) # $true = BOM付き
[System.IO.File]::WriteAllLines("C:\out\report.csv", $csv, $utf8Bom)
一言まとめ:エンコードは「既定任せ」にしない。Excel前提のCSVは PowerShell 7 なら
-Encoding UTF8BOM、5.1 はUTF8Encoding($true)で明示する。
罠2:改行コード ―― 「行数がおかしい」「Get-Content の結果が配列なのか文字列なのか分からない」
症状
-
Get-Contentで読んだ内容を加工して書き戻したら、改行が CRLF と LF で混ざった。Unix系ツールやGitで差分が全行になる。 - ファイル全体を1つの文字列として扱いたいのに、1行ずつの配列になっていて
-replaceがうまく効かない。
原因
Get-Content は**既定で「1行=1要素の配列」**を返します。各要素には改行文字は含まれません。これを Set-Content で書き戻すと、OS既定の改行(Windowsなら CRLF)で連結されます。元がLFのファイルでも、書き戻すとCRLFに変わってしまうのはこのためです。
一方、-Raw を付けるとファイル全体を改行込みの1つの文字列として読みます。配列と文字列では -replace や .Length の意味がまるで変わります。
対策コード
全体を1つの文字列として扱いたいなら -Raw を使います。
# ❌ 既定:1行ずつの配列。複数行にまたがる置換ができない
$text = Get-Content -LiteralPath "C:\in\template.txt"
$text = $text -replace "(株)", "株式会社" # 行ごとには効くが、全体置換のつもりだと挙動が読みにくい
# ✅ -Raw でファイル全体を1つの文字列として読む
$text = Get-Content -LiteralPath "C:\in\template.txt" -Raw
$text = $text -replace "(株)", "株式会社"
改行コードを明示的に統一したいときは、-Raw で読んでから自分で揃えて、-NoNewline で書きます(Set-Content が末尾改行を勝手に足すのを防ぐ)。
# CRLF / LF が混在したファイルを、すべて LF に統一して書き戻す
$text = Get-Content -LiteralPath "C:\in\mixed.txt" -Raw
$text = $text -replace "`r`n", "`n" -replace "`r", "`n" # まずCRLF→LF、次に残ったCR→LF
Set-Content -LiteralPath "C:\out\lf.txt" -Value $text -NoNewline -Encoding UTF8
`r`n(CR+LF)と`n(LF)はPowerShellのエスケープ表記です。先にCRLFを潰してから残ったCRを処理する順番が大事で、逆にすると`r`nが`n`n(空行)に化けます。
一言まとめ:
Get-Contentは既定で「行の配列」。全体を文字列で扱うなら-Raw。改行統一は-replace "\r`n","`n"を先に、書き戻しは-NoNewline`。
罠3:$null比較の順番 ―― 「$x -eq $null が配列で誤動作する」
症状
「変数が空かどうか」を $x -eq $null で判定したら、$x が配列のときに想定外の結果になった。空でないのに空判定された、あるいはその逆。
原因
PowerShell の比較演算子は、**左辺が配列(コレクション)だと「各要素をフィルタして、一致した要素の配列を返す」**という動作をします。つまり $x -eq $null で $x が配列だと、「$null と等しい要素だけを抜き出した配列」が返り、それを if が真偽判定すると直感と違う結果になります。
$x = @(1, $null, 3)
$x -eq $null # → $null(要素のうちnullのものだけが返る)。これを if に渡すと挙動が読みにくい
対策コード
$null を必ず左辺に置くのが定石です。左辺がスカラー($null)なら、配列フィルタ動作は起きず、純粋な「等しいか」の判定になります。
# ❌ 危険:$x が配列だと配列フィルタ動作になる
if ($x -eq $null) { "空です" }
# ✅ 正解:$null を左辺に置く
if ($null -eq $x) { "空です" }
# ✅ 「nullでない」も同様に $null を左
if ($null -ne $x) { "値があります" }
「空文字や空配列も含めて中身が無い」を広く判定したいなら、[string]::IsNullOrWhiteSpace() や、配列なら .Count を併用します(.Count の罠は次の罠4へ)。
このルールはあまりに有名で、PowerShellの静的解析ツール PSScriptAnalyzer にも
PSPossibleIncorrectComparisonWithNullという専用ルールがあるほどです。
一言まとめ:null比較は必ず
$null -eq $xの順($nullを左)。右に置くと配列で静かに壊れる。
罠4:0件・1件の .Count 問題 ―― 「結果が1件だと .Count が取れない/0件で落ちる」
症状
Get-ChildItem や Where-Object の結果に .Count を使ったら、ヒットが0件や1件のときだけ挙動が変わった。1件のときに件数を数えようとしたら、文字列の長さが返ってきた、などの事故。
原因
PowerShellは、**コマンドの結果が0件なら $null、1件なら「配列ではなく単一オブジェクトそのもの」**を返します(パイプラインのアンラップ。罠7と表裏一体)。
- 0件 →
$null。$null.Countは PowerShell 7 では0、5.1 ではエラーになることがある。 - 1件 → 単一オブジェクト。
.Countがそのオブジェクト固有の意味になったり、文字列なら.Length(文字数)的な値になったりする。
対策コード
@( ... )(配列部分式演算子)で囲って、必ず配列に正規化してから数えます。
# ❌ 危険:0件で $null、1件で単一オブジェクトになり .Count が不安定
$files = Get-ChildItem -LiteralPath "C:\work" -Filter "*.log"
if ($files.Count -gt 0) { "ログあり" }
# ✅ 正解:@() で必ず配列にする
$files = @(Get-ChildItem -LiteralPath "C:\work" -Filter "*.log")
if ($files.Count -gt 0) { "$($files.Count) 件のログがあります" }
これで 0件なら Count = 0、1件なら Count = 1 と、件数が常に正しく取れます。第2回(古いログの抽出・削除)でも、「消す対象が0件・1件のとき件数表示が崩れる」ためこの @() 正規化を使いました。
一言まとめ:件数を数える前に
@(...)で配列化。0件=null・1件=単一オブジェクトの罠を消せる。
罠5:Import-Csv の値は全部「文字列」 ―― 「合計したのに 0、平均がおかしい」
症状
Import-Csv で読んだ金額や数量を Measure-Object -Sum で合計したら、合計が0になる/足し算になっていない/並べ替えが文字列順になった。
原因
Import-Csv が返す各列の値は、**見た目が数字でも、すべて文字列(String)**です。文字列のまま数値演算・数値ソートをすると、意図しない結果になります。"10" と "9" を文字列比較すると "10" < "9"(辞書順)になるのと同じ理屈です。
対策コード
演算・ソートの前に [int] / [double] で明示キャストします。第4回(CSV結合・集計)の最重要ポイントでした。
$rows = Import-Csv -LiteralPath "C:\sales\sales.csv" # amount 列は文字列
# ❌ 文字列のまま合計 → 意図通りにならない
($rows | Measure-Object amount -Sum).Sum
# ✅ 数値にキャストしてから合計
($rows | Measure-Object { [double]$_.amount } -Sum).Sum
# ✅ さらに堅い:読み込み直後に数値列を作り直す(入口で数値化)
$rows = Import-Csv -LiteralPath "C:\sales\sales.csv" | ForEach-Object {
[pscustomobject]@{
category = $_.category
amount = [int]$_.amount # ここで数値化しておけば後続がラク
}
}
($rows | Measure-Object amount -Sum).Sum # 以降はキャスト不要
桁区切りカンマ入り("1,500,000")は、そのまま [int] するとエラーになるので、カンマを除去してからキャストします。
[int]($_.amount -replace ',', '')
この罠は PowerShell 7 でも 5.1 でも同じです(バージョンを問わず
Import-Csvの値は文字列)。
一言まとめ:
Import-Csvの値は全部文字列。合計・平均・数値ソートの前に必ず[int]/[double]でキャスト。入口で数値化が定石。
罠6:実行ポリシー ―― 「.ps1 をダブルクリックしても動かない/『このシステムではスクリプトの実行が無効』」
症状
作った .ps1 を実行しようとすると、「このシステムではスクリプトの実行が無効になっているため…」 と赤いエラーで止まる。とくに別PC・サーバー・初回環境でよく起きる。
原因
PowerShellには 実行ポリシー(Execution Policy) という安全装置があり、Windows PowerShell 5.1 の既定はしばしば Restricted(スクリプト実行を全面禁止)です。これはマルウェア対策の仕組みであって、コードの正しさとは無関係に止めてきます。
対策コード
まず現状を確認します。
Get-ExecutionPolicy -List
恒久的に緩めるなら、現在のユーザーだけ RemoteSigned(ローカル作成のスクリプトは許可、ネットから来たものは署名が必要)にするのが現実的な落としどころです。
# 現在のユーザー範囲だけ変更(管理者権限は基本不要)
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
「そのスクリプトを今だけ動かしたい」なら、ポリシーを変えずに起動時の引数で一時的にバイパスできます。タスクスケジューラ登録時にもこの形が定番です。
# このプロセス限り Bypass(システム設定は変えない)
powershell.exe -ExecutionPolicy Bypass -File "C:\scripts\job.ps1"
# PowerShell 7 なら pwsh.exe
pwsh.exe -ExecutionPolicy Bypass -File "C:\scripts\job.ps1"
Bypassは安全装置を外す指定なので、信頼できる自作スクリプトに対してだけ使ってください。配布・共有するスクリプトは、本来は**コード署名(Set-AuthenticodeSignature)**するのが正道です。
一言まとめ:「実行が無効」は実行ポリシー。常用は
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned、単発・タスクは-ExecutionPolicy Bypass -File。
罠7:パイプラインの「アンラップ」 ―― 「1要素だけ返したつもりが配列じゃない/関数の戻り値が配列にならない」
症状
関数やパイプラインから1件だけ返したら、呼び出し側で配列として扱えない。逆に、複数行をまとめて返したつもりが、受け側で「1個の塊」として扱われたり、想定外に展開されたりする。
原因
PowerShellは、コレクションをパイプラインや戻り値に流すと、要素を1個ずつにバラして(アンラップして)流す仕様です。結果が1個になると配列ではなく単一オブジェクトになり(罠4と同根)、戻り値を $result で受けても .Count が安定しません。
対策コード
「常に配列で受けたい」なら、受け側で @(...) で囲むのが最も簡単で確実です(罠4と同じ手)。
# 関数の戻り値を常に配列として扱いたい
$result = @(Get-ActiveUsers)
"$($result.Count) 件"
**「単一要素の配列をそのまま返したい」**なら、,(カンマ演算子)で1要素配列を作って返します。これはアンラップを意図的に1段ぶん打ち消すテクニックです。
function Get-OneItemArray {
$items = @("only")
# ❌ これだと呼び出し側で単一文字列になる(アンラップされる)
# return $items
# ✅ カンマで包むと「1要素の配列」として渡せる
return ,$items
}
$r = Get-OneItemArray
$r.GetType().Name # → Object[](配列として受け取れる)
受け側を自分で書けるなら
@(...)で囲むほうが読みやすく、,は「関数側で配列性を保証したい」特殊ケースで使う、と覚えておくと混乱しません。
一言まとめ:パイプラインは要素をアンラップする。配列で受けたいなら受け側で
@(...)、配列で返したいなら,$arr。
罠8:-Filter と Where-Object の違い ―― 「再帰検索が遅い/件数が多いと固まる」
症状
Get-ChildItem -Recurse | Where-Object { $_.Name -like "*.log" } でファイルを絞ると、大きなフォルダで極端に遅い。同じことを -Filter でやると速い。
原因
-
-Filterは、ファイルシステム側(プロバイダー)で絞り込んでから PowerShell に渡します。余計なオブジェクトを生成しないので速い。ただしワイルドカード程度の単純条件しか書けません。 -
Where-Objectは、いったん全ファイルをオブジェクト化してから PowerShell 側で1個ずつ条件評価します。柔軟(複雑な条件OK)ですが、件数が多いと遅い。 -
-Includeは単独だと効きづらく(-Recurseか末尾\*が前提)、直感に反する挙動でハマりがちです。
対策コード
まず -Filter で粗く・速く絞り、どうしても表現できない条件だけ Where-Object で仕上げるのが定石です。第2回(古いログの抽出)でも、拡張子は -Filter、「N日より古い」は日時計算で Where-Object、と役割分担しました。
# ❌ 遅い:全件オブジェクト化してから拡張子で絞る
Get-ChildItem -LiteralPath "C:\logs" -Recurse |
Where-Object { $_.Extension -eq ".log" }
# ✅ 速い:拡張子の粗い絞り込みは -Filter に任せる
Get-ChildItem -LiteralPath "C:\logs" -Recurse -Filter "*.log"
# ✅ 速い×柔軟:粗く -Filter → 細かい条件だけ Where-Object
$limit = (Get-Date).AddDays(-30)
Get-ChildItem -LiteralPath "C:\logs" -Recurse -Filter "*.log" |
Where-Object { $_.LastWriteTime -lt $limit } # 30日より古いものだけ
-Filterは1つしか指定できず、*.logのような単純パターン専用です。「.logと.txtの両方」のような複数拡張子は、-Filterを欲張らずWhere-Object { $_.Extension -in '.log','.txt' }で受けるか、拡張子ごとに2回Get-ChildItemするのが素直です。
一言まとめ:粗い絞り込みは速い
-Filter、複雑な条件だけWhere-Object。-Recurseの大量検索でWhere-Object一本は遅い。
罠9:相対パスの罠 ―― 「手元では動くのに、タスクスケジューラから実行すると別の場所を見る」
症状
スクリプト内で ".\data\input.csv" のような相対パスを使っていると、**手動実行では動くのに、タスクスケジューラやショートカット、別フォルダからの実行で「ファイルが見つからない」**になる。あるいは出力ファイルが意図しない場所に作られる。
原因
相対パスは 「カレントディレクトリ(現在の作業フォルダ)」を基準に解決されます。ところがカレントディレクトリはどこから起動したかで変わります。タスクスケジューラは既定で C:\Windows\System32 がカレントになることが多く、.\data\input.csv は C:\Windows\System32\data\input.csv を指してしまいます。
対策コード
「スクリプト自身の場所」を基準にするのが正解です。.ps1 ファイル内では自動変数 $PSScriptRoot(そのスクリプトが置かれているフォルダの絶対パス)が使えます。第10・11回でも、運用に乗せるスクリプトはこれで固めました。
# ❌ 危険:カレントディレクトリ依存。起動元で壊れる
$input = ".\data\input.csv"
$output = ".\out\result.csv"
# ✅ 正解:スクリプト自身の場所を基準にする(絶対パス化)
$baseDir = $PSScriptRoot
$input = Join-Path $baseDir "data\input.csv"
$output = Join-Path $baseDir "out\result.csv"
Import-Csv -LiteralPath $input | # ... 以降の処理
Export-Csv -LiteralPath $output -Encoding UTF8BOM -NoTypeInformation
Join-Path を使うと、区切り文字(\ / /)の付け忘れ・重複を気にせず安全に連結できます。
$PSScriptRootは.ps1ファイルとして実行したときに値が入ります(コンソールに直接貼り付けて実行すると空になります)。タスク登録するスクリプトでは、冒頭で$baseDir = $PSScriptRootと置いてから、すべてのパスをJoin-Path $baseDir ...で組むのを習慣にすると事故が激減します。
一言まとめ:スクリプト内のパスは相対にしない。
$PSScriptRoot+Join-Pathで「スクリプト自身の場所」基準の絶対パスにする。
罠10:エラーが既定で握りつぶされる ―― 「途中で失敗してるのに最後まで走って『成功』扱い」
症状
ファイルコピーやAPI呼び出しが途中で1件失敗しても、スクリプトは止まらずに最後まで走り、外形上は正常終了してしまう。バッチ運用で「動いてるつもりが実は半分失敗していた」という最悪の事故につながる。
原因
PowerShellの多くのコマンドレットのエラーは 「非終了エラー(non-terminating error)」 で、既定($ErrorActionPreference = "Continue")では赤い文字を出すだけで処理は続行します。try/catch を書いても、非終了エラーは catch に飛びません。
対策コード
**「止まってほしい操作には -ErrorAction Stop を付け、try/catch で受ける」**のが基本です。第11回(壊れにくいスクリプトの作法)の中核でした。
# ❌ 既定:エラーが出ても続行。catch に来ない
try {
Copy-Item -LiteralPath "C:\in\a.csv" -Destination "D:\backup\a.csv"
} catch {
"失敗したのに、ここには来ない(非終了エラーのため)"
}
# ✅ 正解:-ErrorAction Stop で「終了エラー」に格上げ → catch で確実に捕捉
try {
Copy-Item -LiteralPath "C:\in\a.csv" -Destination "D:\backup\a.csv" -ErrorAction Stop
Write-Host "コピー成功"
} catch {
Write-Warning "コピー失敗: $($_.Exception.Message)"
# ログ出力や、必要なら exit 1 でジョブを失敗扱いにする
exit 1
}
スクリプト全体で一律に止めたいなら、冒頭で優先設定を変える手もあります。
$ErrorActionPreference = "Stop" # 以降、非終了エラーも終了エラーとして扱う
ループ内で「1件失敗しても残りは処理し続けたい。ただし失敗は記録したい」なら、try/catch をループ内に置き、失敗を配列に溜めて最後に件数を報告します。
$failed = @()
foreach ($f in $files) {
try {
Copy-Item -LiteralPath $f.FullName -Destination $dest -ErrorAction Stop
} catch {
$failed += [pscustomobject]@{ File = $f.Name; Reason = $_.Exception.Message }
}
}
if ($failed.Count -gt 0) {
Write-Warning "$($failed.Count) 件失敗しました"
$failed | Format-Table -AutoSize
}
一言まとめ:PowerShellのエラーは既定で握りつぶされる。止めたい操作は
-ErrorAction Stop+try/catch。バッチは「黙って半分失敗」が一番こわい。
おまけ:あと2つ、地味に効く罠
10選には絞りましたが、隣接して踏みやすい罠をあと2つだけ。
おまけA:日付フォーマットとカルチャ依存
Get-Date -Format や .ToString() の書式文字列は大文字小文字で意味が変わります。MM(月・2桁)と mm(分・2桁)、HH(24時間)と hh(12時間)は別物です。ファイル名のタイムスタンプで mm を月のつもりで使うと、分が入ってしまいます。
# ❌ mm は「分」。月のつもりが分になる(月の位置に、実行時刻の「分」が入る)
Get-Date -Format "yyyy-mm-dd" # → 例: 2026-05-30(mm が分なので、05分なら 05 になる)
# ✅ 月は MM、時刻まで入れるなら HH:mm:ss
Get-Date -Format "yyyy-MM-dd_HH-mm-ss" # → 2026-06-30_14-05-09
また、-le / -lt で日付の境界を比較するときは、「その日の23:59まで含めたいのか、翌日0:00未満なのか」を意識します(第2回の「N日より古い」判定で扱った話)。
おまけB:全角スペース・BOM混入による比較失敗
「文字列が一致するはずなのに -eq が False」のとき、犯人は目に見えない文字であることが多いです。全角スペース( )、前後の半角スペース、コピペで紛れ込んだBOM、CRが末尾に残っている、など。
# 比較やキー照合の前に、前後の空白を除去して正規化
$key = $raw.Trim()
# 全角スペースも潰したいなら明示的に置換
$key = ($raw -replace ' ', '').Trim()
CSVの突合(第6回の Compare-Object)で「一致するはずのキーが差分に出る」ときは、まずこの不可視文字の正規化を疑ってください。
シリーズ全12回の総括
最終回なので、全12回を業務フローの流れで振り返ります。「ファイル運用 → データ処理 → 連携・運用 → 総まとめ」という構成でした(各回タイトルへのリンクは Qiitaのプロフィール からたどれます)。
フェーズ1:ファイル運用の自動化(第1〜3回)
-
第1回 大量ファイルの一括リネーム/拡張子別フォルダ振り分け …
Get-ChildItem→Sort-Object→Rename-Item/Move-Item、破壊的操作は-WhatIfでドライラン。-LiteralPathでワイルドカード事故を回避。 -
第2回 古いログファイルの抽出・自動削除 …
LastWriteTimeで「N日より古い」を絞り、-FilterとWhere-Objectを役割分担。0件・1件の.Countを@()で正規化。 -
第3回 フォルダ構成・ファイル一覧をExcel台帳化 …
-Recurse+ 計算プロパティ +Export-Csv -Encoding UTF8BOM。集計はGroup-Object+Measure-Object。
フェーズ2:データ処理の自動化(第4〜7回)
-
第4回 複数CSVを1枚に結合して集計 …
Import-Csvで縦結合、値は文字列なので[int]/[double]でキャスト、クロス集計は複数キーのGroup-Object。 -
第5回 Excelを開かずにセル値を読み書き …
ImportExcelでOpen-ExcelPackage+Cells["B2"].Value、保存はClose-ExcelPackage -Save。.xlsxは文字コード問題が無い。 -
第6回 2つのリストを突合して差分を出す …
Compare-Objectで「増えた・減った・変わった」を抽出。キーの不可視文字(全角スペース・BOM)に注意。 -
第7回 テキスト・ログからの抽出と整形 …
Select-String(grep相当)と正規表現で必要な行・項目だけを取り出す。
フェーズ3:連携・運用への接続(第8〜11回)
-
第8回 フォルダ監視・定期実行で「気づいたら処理」 …
FileSystemWatcher/ タスクスケジューラ連携で自動起動。 - 第9回 結果をメール・通知で飛ばす … 処理結果のサマリを通知し、人が見にいかなくても気づける仕組みに。
-
第10回 設定の外出し・パラメーター化と再利用 … パスや閾値を
$PSScriptRoot基準の設定で外出しし、環境差を吸収。 -
第11回 壊れにくいスクリプトの作法 …
-ErrorAction Stop+try/catch、ログ出力、-WhatIf/-Confirm、絶対パス化で「黙って失敗しない」運用へ。
フェーズ4:総まとめ(第12回・本記事)
-
第12回 現場でハマる罠10選 … 文字コード/改行/
$null比較/.Count/キャスト漏れ/実行ポリシー/アンラップ/-FiltervsWhere-Object/相対パス/エラー握りつぶし。これまでの注意点を「症状→原因→対策」で一望できる形に再整理しました。
締めの言葉
このシリーズは一貫して、「手作業を、コピペで動く検証済みスクリプトに置き換える」ことだけを目的にしてきました。難しい文法を覚えることより、「これは自動化できる作業だ」と気づけること、そして事故らずに安全に回せることのほうが、現場ではずっと価値があります。
最終回でまとめた10の罠は、どれも「知っていれば一生踏まない/知らないと何度でも踏む」類のものばかりです。最初の数回は意識して、@() で配列化する・$null を左に置く・Import-Csv の値はキャストする・パスは $PSScriptRoot 基準にする——を指が勝手に動くレベルまで馴染ませてください。そこまでくれば、PowerShellは「たまに使う便利ツール」から「毎日の手間を肩代わりしてくれる相棒」に変わります。
全12回、お付き合いいただきありがとうございました。あなたの現場の「地味だけど毎回つらい作業」が、1つでも多くスクリプト一本に置き換わることを願っています。それでは、よい自動化ライフを。
参考
- about_Comparison_Operators($null 比較・配列比較)- Microsoft Learn
- about_Arrays(
@()配列部分式・,カンマ演算子)- Microsoft Learn - about_Execution_Policies - Microsoft Learn
- about_Preference_Variables($ErrorActionPreference / $WhatIfPreference)- Microsoft Learn
- about_Try_Catch_Finally - Microsoft Learn
- about_Automatic_Variables($PSScriptRoot)- Microsoft Learn
- about_Character_Encoding - Microsoft Learn
- Get-ChildItem(-Filter)- Microsoft Learn
@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!