PowerShellからPostgreSQLのSQLを実行する方法|psql接続・引数・エラー停止

PowerShellからPostgreSQLへ接続し、SQLファイルを実行するには、PostgreSQLのコマンドラインツール psql を使えます。実行ファイルのパスと引数を別々に渡し、SQLのエラーで停止する設定と終了コードの確認を組み合わせるのが基本です。まず読み取り専用の接続確認を行い、その後にレビュー済みのSQLファイルを実行します。

実行ファイルと引数を一つの文字列にまとめると、PowerShellの呼び出し演算子では正しく実行できません。

目次

最初に確認するもの:PowerShell・psql・接続先

  • この記事の例はPowerShell 7.3以降を前提にします。Windows PowerShell 5.1では外部コマンドの引数やエラー出力の扱いが異なるため、同じログ処理がそのまま動くとは扱いません。
  • PostgreSQLサーバーへ接続できるネットワーク、データベース名、ホスト名、ポート、実行に必要な権限を持つDBユーザーを用意します。
  • 例の localhost、5432、demo、app_migration、SQLファイルのパスは自分の環境へ置き換えます。変更を伴うSQLは検証用DBで確認し、復旧手段を用意してから本番へ適用します。

PowerShellは利用するOSに対応した方法で導入し、PostgreSQLクライアントは PostgreSQL公式ダウンロード から用意します。Windowsの配布経路として Microsoft Store を利用する場合も、実際に起動したPowerShellの版を確認してください。psqlによる実行に、追加のPowerShellデータベース管理モジュールは必須ではありません。

$PSVersionTable.PSVersion

psqlがPATHへ登録されているか確認します。見つからない場合は、インストール先の bin フォルダーを確認するか、実際の psql.exe の絶対パスを後の変数へ指定します。サーバーが別PCにあっても、このPCにはクライアントが必要です。

psql --version

実行対象を解決し、表示されたパスを確認します。PATHへ登録しただけで別バージョンのpsqlが選ばれる場合もあります。

$psqlExe = (Get-Command psql -CommandType Application -ErrorAction Stop).Source
$psqlExe

PowerShellからPostgreSQLへ接続する最小例

次は接続先のDB名とユーザーを表示するだけの確認です。-h はホスト、-p はポート、-U はDBユーザー、-d はDB名です。対話実行では必要に応じてパスワードの入力を求められます。

& $psqlExe -X -v ON_ERROR_STOP=1 -h localhost -p 5432 -U app_migration -d demo -c 'SELECT current_database(), current_user;'
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { throw "接続確認に失敗しました: exit=$exitCode" }

パスに空白がある場合も、パスだけを変数へ入れ、引数を分けます。以下のインストール先は例なので、存在するパスへ変更してください。Microsoftの 呼び出し演算子の説明 は、& がコマンドと引数を含む文字列を分解しないことを明記しています。

$psqlExe = 'C:\Program Files\PostgreSQL\18\bin\psql.exe'
& $psqlExe --version

引数が多い場合は配列を使います。コマンド全体を文字列として組み立てて Invoke-Expression へ渡す必要はありません。-X はpsqlの起動設定ファイルを読み込まない指定です。

$connectionArgs = @('-X', '-v', 'ON_ERROR_STOP=1', '-h', 'localhost', '-p', '5432', '-U', 'app_migration', '-d', 'demo')
& $psqlExe @connectionArgs -c 'SELECT current_database(), current_user;'
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { throw "psql failed: exit=$exitCode" }

無人実行の認証:pgpass.confと実行ユーザー

定期処理では入力待ちを避けるため、認証方法を先に用意します。Windowsでの標準パスワードファイルは、実行ユーザーの %APPDATA%\postgresql\pgpass.conf です。PGPASSFILE で別ファイルを指定することもできます。タスクの実行ユーザーが自分と異なる場合、参照するプロファイルも異なります。

$passwordFile = Join-Path $env:APPDATA 'postgresql\pgpass.conf'
$passwordFile
Test-Path -LiteralPath $passwordFile

ファイルの行形式は hostname:port:database:username:password です。接続条件と一致する最初の行が使われ、値に含まれるコロンとバックスラッシュはエスケープが必要です。平文の認証情報なので、実行ユーザー以外が読めない保存先とアクセス権を確認します。Unixでは所有者以外のアクセスを禁止する権限が必要です。詳細は PostgreSQLのパスワードファイル を参照してください。

公式資料は PGPASSWORD を環境変数に置く方法を安全上の理由から推奨していません。パスワードをスクリプト本文やコマンド、ログに埋め込む手順から、パスワードファイル等の管理へ切り替えてください。根拠は libpqの環境変数 です。

認証設定後の確認では -w を付けます。これは「パスワード不要にする」設定ではなく、入力を求めず、必要な認証情報がなければ接続を失敗させる設定です。

$batchArgs = $connectionArgs + @('-w')
& $psqlExe @batchArgs -c 'SELECT current_database(), current_user;'
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { throw "無人実行の接続確認に失敗しました: exit=$exitCode" }

SQLファイルを実行する:対象確認・順序・エラー停止

SQLファイルは -f で指定します。フォルダー内のファイルを無条件で全部適用する前に、絶対パスと順序を確認します。連番は桁をそろえ、001_schema.sql、010_data.sql のようにして名前順の意図を明確にします。

$sqlDirectory = 'C:\db scripts\reviewed'
$sqlFiles = @(Get-ChildItem -LiteralPath $sqlDirectory -Filter '*.sql' -File -ErrorAction Stop | Sort-Object Name)
if ($sqlFiles.Count -eq 0) { throw 'SQLファイルがありません。' }
$sqlFiles | Select-Object Name, FullName

次の関数は、psqlの出力と終了コードを別々に返します。PowerShellの例外だけに頼らず、外部コマンドの終了コードを直後に確保します。関数内で外部コマンドの自動エラー化を無効にし、戻り値で失敗を判定する方式を統一しています。

function Invoke-PsqlFile {
    param([string]$Executable, [string[]]$BaseArguments, [string]$SqlFile)
    $ErrorActionPreference = 'Stop'
    $PSNativeCommandUseErrorActionPreference = $false
    if (-not (Test-Path -LiteralPath $SqlFile -PathType Leaf)) {
        throw "SQLファイルが見つかりません: $SqlFile"
    }
    $psqlArguments = $BaseArguments + @('-f', $SqlFile)
    $outputLines = & $Executable @psqlArguments 2>&1
    $exitCode = $LASTEXITCODE
    [pscustomobject]@{ File = $SqlFile; ExitCode = $exitCode; Output = @($outputLines) }
}
$result = Invoke-PsqlFile -Executable $psqlExe -BaseArguments $batchArgs -SqlFile $sqlFiles[0].FullName
$result.Output
if ($result.ExitCode -ne 0) { throw "SQL実行失敗: exit=$($result.ExitCode)" }

この例は最初の1ファイルを実行します。実行例を繰り返すと同じSQLを再適用するため、コードを上から全部一度に実行しないでください。接続先とSQLの再実行可否を確認し、必要な例だけを選びます。

出力をログに保存する場合は、終了コードを取得した後に記録します。DBのエラー出力には内部名やSQLが含まれることがあるため、ログもアクセスを管理します。

$logFile = 'C:\db scripts\logs\run.log'
# logs フォルダーは事前に用意する
$result.Output | ForEach-Object { "$_" } | Add-Content -LiteralPath $logFile -Encoding utf8
Add-Content -LiteralPath $logFile -Value ('file={0}; exit={1}' -f $result.File, $result.ExitCode) -Encoding utf8

順序を固定したい場合は、レビュー済みのファイルを明示的な配列で指定できます。名前順に依存する場合も、実行前に一覧を確認してください。

$approvedFiles = @('C:\db scripts\reviewed\001_schema.sql', 'C:\db scripts\reviewed\010_data.sql')
$approvedFiles | ForEach-Object { Get-Item -LiteralPath $_ -ErrorAction Stop } | Select-Object FullName

psqlが失敗したことをPowerShellで判定する

SQLでエラーが起きても、標準設定のpsqlは処理を継続します。-v ON_ERROR_STOP=1 と終了コードの両方を確認してください。-f 実行では、エラーの行番号も確認できます。仕様は psqlのオプション・終了ステータス に示されています。

$exitMeanings = @{ 0 = '正常終了'; 1 = 'psql自身の致命的エラー'; 2 = '非対話セッションでの接続異常'; 3 = 'ON_ERROR_STOPによるスクリプトエラー停止' }
$exitMeanings[$result.ExitCode]

PowerShellの $LASTEXITCODE は最後に動いた外部プログラム等の終了コードです。他の外部コマンドを実行する前に保存し、ファイル名と対応付けます。Start-Process の起動直後や、古い値を読み続ける設計でSQL成功を判定しないでください。根拠は 自動変数の仕様 です。

& $psqlExe @batchArgs -f 'C:\db scripts\reviewed\001_schema.sql'
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { throw "psql failed: exit=$exitCode" }

依存関係のある複数SQLを個別に実行する場合は、最初の失敗で止めます。このループでは先に成功したファイルの変更は残ります。途中停止と全体のロールバックは別です。

foreach ($sqlFile in $approvedFiles) {
    $result = Invoke-PsqlFile -Executable $psqlExe -BaseArguments $batchArgs -SqlFile $sqlFile
    $result.Output
    if ($result.ExitCode -ne 0) { throw ('SQL失敗: {0}; exit={1}' -f $sqlFile, $result.ExitCode) }
}

一つの失敗を無視して次へ進めるのは、各処理が独立し、失敗一覧を後から確認できる場合に限ります。前段のテーブル作成が失敗したまま後段の投入を進める運用には向きません。

$failedFiles = [System.Collections.Generic.List[string]]::new()
foreach ($sqlFile in $approvedFiles) {
    $result = Invoke-PsqlFile -Executable $psqlExe -BaseArguments $batchArgs -SqlFile $sqlFile
    if ($result.ExitCode -ne 0) { $failedFiles.Add($sqlFile) }
}
if ($failedFiles.Count -gt 0) { throw ('失敗したSQL: ' + ($failedFiles -join ', ')) }

ログとトランザクションをどう設計するか

少なくとも開始時刻、接続先の識別情報、対象ファイル、終了コード、失敗時の出力を残します。パスワードや接続URL内の認証情報は記録しません。大量の出力を配列へ保持するこの例は小規模な処理向けで、長時間・大量データのジョブではログ容量や出力の保存方法を別途設計してください。

Add-Content -LiteralPath $logFile -Value ('time={0:o}; db=demo; file={1}; exit={2}' -f (Get-Date), $result.File, $result.ExitCode) -Encoding utf8

psqlの -e は送信SQLを表示する指定で、失敗を自動的に直すデバッグ機能ではありません。検証時にSQLの表示が必要なときだけ使い、機密値を含むSQLの出力先を確認してください。

# 検証用の読み取り専用SQLを表示する例
& $psqlExe @batchArgs -e -c 'SELECT current_database();'
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { throw "psql failed: exit=$exitCode" }

複数ファイルを一つのトランザクションにまとめたい場合は、psqlを一度起動し、--single-transaction と複数の -f を渡す方法があります。ON_ERROR_STOP も指定します。ただしSQL自身にBEGIN・COMMIT・ROLLBACKがある場合や、トランザクション内で実行できない命令を含む場合は、意図した一括処理になりません。SQL内容の確認なしに追加しないでください。


タスクスケジューラから実行する場合

実行対象を Run-Sql.ps1 として保存する場合は、psqlとSQLの絶対パス、無人認証、ログ保存先、失敗時の終了コードをスクリプト内で完結させます。ここまでの例で使った変数や関数が、別プロセスのタスクへ自動で引き継がれることはありません。次は、その構成を一つのファイルにまとめる例です。

# Run-Sql.ps1 / PowerShell 7.3 以降
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $false
$psqlExe = 'C:\Program Files\PostgreSQL\18\bin\psql.exe'
$sqlFile = 'C:\db scripts\reviewed\001_schema.sql'
$logFile = 'C:\db scripts\logs\scheduled.log'
$runArgs = @('-X', '-w', '-v', 'ON_ERROR_STOP=1', '-h', 'localhost', '-p', '5432', '-U', 'app_migration', '-d', 'demo', '-f', $sqlFile)
try {
    if (-not (Test-Path -LiteralPath $psqlExe -PathType Leaf)) { throw 'psql.exeがありません。' }
    if (-not (Test-Path -LiteralPath $sqlFile -PathType Leaf)) { throw 'SQLファイルがありません。' }
    $outputLines = & $psqlExe @runArgs 2>&1
    $exitCode = $LASTEXITCODE
    $outputLines | ForEach-Object { "$_" } | Add-Content -LiteralPath $logFile -Encoding utf8
    Add-Content -LiteralPath $logFile -Value ("exit=$exitCode") -Encoding utf8
    exit $exitCode
} catch {
    [Console]::Error.WriteLine($_.Exception.Message)
    exit 1
}

タスクの「プログラム/スクリプト」は実際の pwsh.exe の絶対パス、「引数」は -NoProfile -NonInteractive -File "C:\db scripts\Run-Sql.ps1" のようにします。タスクの実行ユーザーでpsql・SQL・ログ・パスワードファイルへアクセスできることを確認します。実行ポリシーや組織の制限を一律に解除する手順は使いません。まず同じアカウント・同じ起動条件で手動確認し、その後に定期実行を設定します。

実行結果が0でも、想定DBに想定変更が適用されたかを確認します。重複実行を避けるタスク設定、SQLの再実行可否、途中失敗時の再開位置も決めておいてください。


LinuxのcronからPowerShellを呼ぶ場合

LinuxでもPowerShellとpsqlをOSに対応した方法で導入します。Windowsのファイルパスや %APPDATA% をそのまま使わず、実行ユーザーのパスワードファイル、実行権限、環境変数を確認してください。cronでは対話シェルとPATHが異なる可能性があるため、実行ファイルとスクリプトの絶対パスを確認します。

Get-Command pwsh, psql -CommandType Application | Select-Object Name, Source

次はcronの登録行の形だけを示します。PowerShellの命令ではありません。実際に確認したパスへ変更し、ログ保存先の権限とSQLの繰り返し実行可否を確認してから、cron側の設定に登録します。

# cron の設定行の例(PowerShellへ入力しない)
# 0 1 * * * /usr/bin/pwsh -NoProfile -NonInteractive -File /opt/db/Run-Sql.ps1 >> /var/log/db/run.log 2>&1

失敗通知・条件付き実行を追加する前に

通知はまず終了コードとログを監視する仕組みにつなぎます。旧例の Send-MailMessage はMicrosoftが非推奨としており、安全なSMTP接続を保証しないと案内しています。新規の無人通知は組織で利用する認証方式に対応した仕組みを選んでください。根拠は Send-MailMessageの公式注意事項 です。

# スケジューラや監視処理へ非0を返す。通知の認証設定は別途行う。
if ($result.ExitCode -ne 0) {
    [Console]::Error.WriteLine(('SQL失敗: {0}; exit={1}' -f $result.File, $result.ExitCode))
    exit $result.ExitCode
}

テーブルの存在などで実行条件を分ける場合は、問い合わせ自体の失敗と条件不一致を分けます。次は表示装飾を省いてtrue/falseを取得する例です。ただし確認と更新の間に状態が変化する可能性があるため、整合性が必要な判定はSQL側の制約やトランザクションも検討してください。

$existsOutput = & $psqlExe @batchArgs -A -t -c "SELECT to_regclass('public.my_table') IS NOT NULL;"
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { throw "存在確認に失敗しました: exit=$exitCode" }
$tableExists = ("$existsOutput".Trim() -eq 't')
$tableExists

実行できないときの確認順

症状先に確認するもの
psqlというコマンドが見つからないPATH、Get-Commandの結果、実際の実行ファイルのパス
接続拒否・タイムアウトホストとポート、DBサービス、ネットワーク、接続を許可する設定
認証に失敗するDBユーザー・DB名、実行ユーザー、パスワードファイルの接続条件
SQLエラー後も次へ進むON_ERROR_STOP指定、取得した終了コード、ループ側の停止判定
手動では動くがタスクでは失敗PowerShellの版、絶対パス、実行ユーザー、入力待ち、ログのアクセス権
途中まで適用されてしまう各ファイルのトランザクション境界、一括トランザクションの適用条件、再実行方針

PowerShell 7.3以降では外部コマンドへの引数の渡し方が変更されています。特殊な引用符や空の引数を含むSQL、既存の5.1用スクリプトを移行する場合は PowerShellの解析と引数渡し を確認します。また、外部コマンドの非0終了をPowerShellエラーとして扱う設定は 設定変数の仕様 に従い、明示的な終了コード判定と混在させず設計します。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次