pipipipi.net

pipipipi.net

約7分で読めます

PowerShellスクリプト入門 — 変数・ループ・条件分岐で作業自動化

PowerShellスクリプト入門 — 変数・ループ・条件分岐で作業自動化

コマンドを1行ずつ打つだけでもPowerShellは便利ですが、複数の処理をまとめて.ps1ファイルに書いておけば、いつでも同じ手順を再現できます。ファイルの一括リネームやログ収集のような繰り返し作業は、スクリプト化しておくメリットが特に大きい分野です。

この記事では、PowerShellスクリプトを書くために最低限知っておきたい構文を、実務で使える例とともに解説します。

.ps1ファイルの作成と実行

PowerShellスクリプトは拡張子.ps1のテキストファイルです。メモ帳やVS Codeなどで作成し、PowerShell上で次のように実行します。

.\myscript.ps1

初めて.ps1ファイルを実行しようとすると、次のようなエラーが出ることがあります。

このシステムではスクリプトの実行が無効になっているため...

これはPowerShellの実行ポリシーによる制限です。既定では、作成者の署名がないスクリプトの実行がブロックされます。自分で書いたスクリプトを実行できるようにするには、管理者権限は不要な、現在のユーザーだけに適用される設定に変更します。

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

RemoteSignedは、ローカルで作成したスクリプトはそのまま実行を許可し、インターネットからダウンロードしたスクリプトには署名を要求するポリシーです。ダウンロードしたスクリプトを1回だけ実行したい場合は、Unblock-Fileでブロックを解除するか、次のように実行ポリシーをその場限りで上書きする方法もあります。

powershell.exe -ExecutionPolicy Bypass -File .\downloaded.ps1

変数の使い方

PowerShellの変数は先頭に$を付けて宣言します。型宣言は不要で、代入した値に応じて自動的に型が決まります。

$name = "設定ファイル"
$count = 5
$files = Get-ChildItem .\logs\

文字列の中に変数の値を埋め込みたいときは、ダブルクォートの中に直接書けます。

$folder = "backup"
Write-Host "対象フォルダ: $folder"

if/else による条件分岐

条件によって処理を分けたいときはif/elseif/elseを使います。

$fileCount = (Get-ChildItem .\logs\).Count

if ($fileCount -eq 0) {
    Write-Host "ログファイルが見つかりません"
} elseif ($fileCount -gt 100) {
    Write-Host "ログファイルが100件を超えています。整理を検討してください"
} else {
    Write-Host "$fileCount 件のログファイルがあります"
}

PowerShellの比較演算子は記号ではなく-eq(等しい)、-gt(より大きい)、-lt(より小さい)、-ne(等しくない)のような形式で書きます。C言語系の==や>とは書き方が異なるので、最初は戸惑いやすいポイントです。

foreach / for によるループ

複数のファイルやデータを1件ずつ処理したいときはforeachを使います。

$files = Get-ChildItem .\downloads\ -File

foreach ($file in $files) {
    Write-Host "処理中: $($file.Name)"
}

回数を指定して繰り返したい場合はforループも使えます。

for ($i = 1; $i -le 5; $i++) {
    Write-Host "$i 回目の処理"
}

パイプラインと組み合わせて、Get-ChildItemの結果を直接ForEach-Objectに渡す書き方もよく使われます。

Get-ChildItem .\downloads\ -File | ForEach-Object {
    Write-Host "処理中: $($_.Name)"
}

$_は、パイプラインで渡されてきた現在の要素を指す自動変数です。

Get-ChildItem でのファイル一覧取得

Get-ChildItemはフォルダ内のファイルやサブフォルダを取得するコマンドです。lsやdirのエイリアスとしても使われますが、スクリプト内ではGet-ChildItemと書くほうが意図が明確になります。

# フォルダのみ取得
Get-ChildItem .\downloads\ -Directory

# ファイルのみ取得
Get-ChildItem .\downloads\ -File

# 特定の拡張子だけに絞る
Get-ChildItem .\downloads\ -File -Filter *.log

# サブフォルダも含めて再帰的に取得
Get-ChildItem .\downloads\ -Recurse -File

実用例1: ファイルの一括リネーム

フォルダ内の画像ファイルに連番を付けてリネームする例です。

$files = Get-ChildItem .\photos\ -File -Filter *.jpg
$i = 1

foreach ($file in $files) {
    $newName = "photo_{0:D3}.jpg" -f $i
    Rename-Item -Path $file.FullName -NewName $newName
    $i++
}

{0:D3}は数値を3桁のゼロ埋めで表示する書式指定です($i=5なら”005”)。実行前に、対象フォルダとフィルター条件が意図通りかを必ず確認してください。Rename-Itemは元に戻せない操作なので、初回は-WhatIfオプションを付けて動作だけ確認する方法も安全です。

Rename-Item -Path $file.FullName -NewName $newName -WhatIf

実用例2: ログファイルのバックアップ収集

複数のアプリケーションのログを1つのバックアップフォルダにまとめてコピーする例です。

$backupRoot = "D:\log_backup\$(Get-Date -Format 'yyyyMMdd')"
New-Item -Path $backupRoot -ItemType Directory -Force | Out-Null

$logFolders = @(
    "C:\App1\logs",
    "C:\App2\logs"
)

foreach ($folder in $logFolders) {
    if (Test-Path $folder) {
        $appName = Split-Path $folder -Parent | Split-Path -Leaf
        $dest = Join-Path $backupRoot $appName
        Copy-Item -Path $folder -Destination $dest -Recurse
        Write-Host "$appName のログをバックアップしました"
    } else {
        Write-Host "$folder が見つかりません。スキップします"
    }
}

New-Item -Forceは、同名のフォルダがすでに存在してもエラーにせず処理を続けます。Test-Pathで事前にフォルダの存在を確認しているのは、対象フォルダが存在しない環境でスクリプト全体が止まらないようにするためです。このスクリプトをタスクスケジューラに登録すれば、毎日決まった時刻にログを自動収集できます。

Write-Host と Write-Output の違い

どちらも画面に文字を表示できますが、役割が異なります。

  • Write-Host: 画面に表示するためだけのコマンドです。出力はパイプラインに乗らず、変数に代入することもできません。進捗メッセージやユーザー向けの案内表示に向いています。
  • Write-Output: パイプラインに値を送り出すコマンドです。後続のコマンドに渡したり、変数に格納したり、ファイルにリダイレクトしたりできます。
# 画面表示専用。$resultには何も入らない
$result = Write-Host "処理中です"

# パイプラインに乗る。$resultに文字列が入る
$result = Write-Output "処理中です"

実際には、値を単独の行に書くだけでも暗黙的にパイプラインへ送られるため、Write-Outputを明示的に書く場面はそれほど多くありません。「これは人間に見せるだけの表示か、それとも後続処理に渡すデータか」を意識して使い分けるのがポイントです。

try/catch によるエラーハンドリング

ファイル操作やネットワーク処理はエラーが起きる可能性があるため、try/catchで捕捉しておくとスクリプトが途中で落ちにくくなります。

try {
    Copy-Item -Path "C:\source\data.csv" -Destination "D:\backup\data.csv" -ErrorAction Stop
    Write-Host "コピーが完了しました"
} catch {
    Write-Host "エラーが発生しました: $($_.Exception.Message)"
}

ポイントは-ErrorAction Stopです。PowerShellの多くのコマンドは、既定ではエラーが起きても処理を継続してしまい、catchブロックに処理が移りません。-ErrorAction Stopを付けることで、そのコマンドのエラーを確実に例外として扱い、catchで捕捉できるようになります。

まとめ

  • スクリプトは.ps1ファイルに保存し、実行ポリシーの設定を1回済ませておく
  • 変数は$記号、条件分岐は-eqや-gtなどの比較演算子を使う
  • foreachやGet-ChildItemを組み合わせれば、フォルダ内の複数ファイルへの一括処理が書ける
  • Write-Hostは表示専用、Write-Outputはパイプライン向けと使い分ける
  • try/catchと-ErrorAction Stopで、想定外のエラーによるスクリプトの中断を防ぐ

まずは今回のリネーム例やバックアップ例のように、自分の日常業務で繰り返している作業を1つスクリプト化してみると、応用が効くようになります。

関連記事