動くコード図鑑技術記事現場の渡り方キャリア論すべての記事About
技術記事

PowerShell から SQL Server にクエリを投げる — Invoke-Sqlcmd と SqlClient の使い分けと業務SEが踏む罠

バイブス父さん
現役の業務SE
2026年7月17日10 min read
PowerShell から SQL Server にクエリを投げる — Invoke-Sqlcmd と SqlClient の使い分けと業務SEが踏む罠

みなさんこんにちは!ヒロポンです!

運用スクリプトから SQL Server にクエリを投げてデータを取りたい。バッチで更新もかけたい。で、PowerShell からどう書くのが正解なんや??って、最初に手が止まりますよね。

正直、俺は昔これで痛い目を見ました。SqlClient で接続を開いたまま閉じ忘れて、本番の接続プールを枯らして、関係ない他の処理まで巻き込んで止めたことがあります。

PowerShell から SQL Server に行く道は、ざっくり2つ。Invoke-Sqlcmd(お手軽)と System.Data.SqlClient(.NET そのまま)です。

今回は、PowerShell SQL Server クエリの2つの道の使い分けと、業務SEが踏みやすい罠を5つ整理します。Windows Server / PowerShell 5.1 前提でいきます。

⏱ 2つの道と罠のサマリ(先に全体像)

細かい話に入る前に、地図だけ先に置いときます。

一言 向いてる場面
Invoke-Sqlcmd 1コマンドで手軽 手早く1発クエリ・結果をすぐ使う
System.Data.SqlClient .NET を直に叩く 依存を増やさず細かく制御したい

踏みやすい罠は、SqlServer モジュール依存 / 接続の閉じ忘れ / 文字列連結のインジェクション / 認証方式 / 大量取得のメモリ。この5つを順に潰していきます。

道A: Invoke-Sqlcmd — 1コマンドで手軽

SqlServer モジュールの Invoke-Sqlcmd は、1行でクエリを投げて結果まで受け取れます。

Import-Module SqlServer   # 事前に入っている前提

$rows = Invoke-Sqlcmd `
    -ServerInstance "localhost" `
    -Database "MyDb" `
    -Query "SELECT Id, Name FROM dbo.Customers WHERE Region = 'Tokyo'"

$rows | ForEach-Object { $_.Name }

結果は DataRow の配列で返ってくるので、$_.Name みたいに列名でそのまま取れます。こんな感じで、手早く1発クエリを投げたい時はこれが一番ラク。

クエリに外部から来る値(引数やファイル)を混ぜたい時は注意が要ります。Invoke-Sqlcmd は後述の罠③のとおり真のパラメータ化ができないので、未信頼の値を渡すなら道B(SqlClient)に寄せるのが安全です。

道B: System.Data.SqlClient — 依存なしで細かく制御

.NETSqlConnection / SqlCommand を直に使う道。モジュール依存が無くて、タイムアウトやトランザクションまで細かく握れます。

$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Server=localhost;Database=MyDb;Integrated Security=True"

try {
    $conn.Open()
    $cmd = $conn.CreateCommand()
    $cmd.CommandText = "SELECT Id, Name FROM dbo.Customers WHERE Region = @r"
    $cmd.Parameters.AddWithValue("@r", "Tokyo") | Out-Null

    $reader = $cmd.ExecuteReader()
    while ($reader.Read()) {
        $reader["Name"]
    }
}
finally {
    $conn.Close()   # ← 必ず閉じる(後述の罠②)
}

長く見えますが、やってるのは「開く → 実行 → 読む → 閉じる」だけ。依存モジュールを入れられない現場や、細かい制御が要る時はこっち。こんな感じで、開いてから閉じるまで全部自分で握ります。

罠①: Invoke-Sqlcmd は SqlServer モジュールが要る

Invoke-SqlcmdSqlServer モジュール(または旧 SQLPS)に入っているコマンドです。モジュールが無い環境では、そもそも動きません

# 入ってなければインストール(管理者権限・オンライン環境)
Install-Module -Name SqlServer -Scope AllUsers

閉じたイントラや、勝手にモジュールを入れられない現場だと、ここがネックになります。そういう時は、モジュール不要な道B(SqlClient)に寄せるのが確実です。

罠②: SqlClient は接続を閉じないと、プールが枯れる

俺が本番でやらかしたのがこれ。SqlConnection.Open() したのに Close() を忘れると、接続が返却されず、接続プールが枯渇します。

こうなると、他の処理も「接続が取れない」で軒並み止まる。たった1つの閉じ忘れが、システム全体を巻き込む事故になるわけです。

対策は、必ず try / finallyfinallyClose() する。途中でエラーが飛んでも確実に閉じるためです。

try {
    $conn.Open()
    # クエリ実行...
}
finally {
    $conn.Close()   # 例外が出ても、ここは必ず通る
}

Invoke-Sqlcmd のほうは接続を内部で開いて閉じてくれるので、この罠は道A側では起きません。閉じ忘れが怖いなら道A、という選び方もアリ。事故りやすさで選ぶのも、いい感じに効く手だと思います。

罠③: 文字列連結はインジェクション — 真のパラメータ化は道B

一番やっちゃいけないのがこれ。クエリに変数を文字列連結で埋め込むと、SQL インジェクションの穴になります。

# ❌ 絶対ダメ。$region に「'; DROP TABLE ...」が入ったら終わり
$query = "SELECT * FROM dbo.Customers WHERE Region = '$region'"

対策は値をパラメータとして渡すこと。ただし、ここは道AとBで意味が全然違うので要注意です。

  • 道B の SqlParameterAddWithValue)は「真のパラメータ化」。値は SQL のテキストではなくパラメータ値としてバインドされるので、injection を防げます。
  • 道A の Invoke-Sqlcmd -Variable は injection 対策になりません-Variable は sqlcmd スクリプト変数で、クエリ側は $(名前) で参照するただのテキスト置換。悪意ある SQL が入れば、そのまま展開されて刺さります(@変数 にも束縛されません)。
# ✅ 道B: パラメータで渡す(値は SQL として解釈されない)
$cmd.Parameters.AddWithValue("@r", $region) | Out-Null

だから、外から来る値(ファイル・引数・DB・画面)をクエリに混ぜるなら、道B のパラメータ化に寄せる。道A は「値が固定 or 自分で握ってる信頼できる値」の1発クエリに留めるのが鉄則です!!

罠④: Windows 認証と SQL 認証で、接続文字列が違う

接続文字列は、認証方式で書き方が変わります。ここを取り違えると「ログインできない」で詰まる。

  • Windows 認証: Integrated Security=True(実行アカウントの権限で接続)
  • SQL 認証: User Id=sa;Password=xxxx(SQL Server のログインで接続)
# Windows 認証(実行アカウントに DB 権限が要る)
"Server=localhost;Database=MyDb;Integrated Security=True"

# SQL 認証
"Server=localhost;Database=MyDb;User Id=appuser;Password=$pw"

タスクスケジューラで動かす時は、実行アカウントに DB のアクセス権があるかを先に確認してください。手だと動くのに、タスクだと落ちる。ん?手で叩くと繋がるのに??ってなるやつの正体は、たいていこれです。

罠⑤: 大量結果の一括取得で、メモリを圧迫する

Invoke-SqlcmdSqlDataAdapter で数百万行を一括取得すると、全部メモリに載って PowerShell が一気に重くなります。

大量データを流すなら、道B の SqlDataReader1行ずつ読んで処理する(前ページの while ($reader.Read()))のが軽い。取得しながら順に捌けば、全行をメモリに抱えずに済みます。

まとめ・チートシート

2つの道を1枚にまとめると、こうです。

PowerShell から SQL Server にクエリを投げる2手段(Invoke-Sqlcmd / System.Data.SqlClient)を、依存・手軽さ・制御の細かさ・戻り値・接続の開閉・パラメータ化・本命用途で比較した表

判断はシンプルで、信頼できる値の手早い1発なら Invoke-Sqlcmd、未信頼の値を扱う or 細かく制御するなら SqlClient。外から来る値をクエリに混ぜるなら、injection を防げる道B のパラメータ化に寄せる。

PowerShell の運用自動化まわりを腰を据えて押さえたいなら、スクリプト系の解説書を1冊持っておくといいです。この手の「どう書くのが定番か」を調べる時間が、まるっと浮きます。

動作確認メモ: この記事のコードは Windows + SQL Server + PowerShell 5.1 前提で、構文と API の妥当性は検証済みです。Invoke-Sqlcmd(SqlServer モジュール)や実 DB 接続は開発機の Linux では動かせないので、実機での動作は別途お試しください。

このチートシート、DB を叩くスクリプトを書く時の手元メモにどうぞ。

よくある質問

Q1. Invoke-Sqlcmd と SqlClient、初心者はどちらから始めるべきですか?

まずは Invoke-Sqlcmd からで十分です。1コマンドで結果が返るので、SQL の確認や単発の集計をすぐ試せます。接続の開閉を自分で管理しなくていいぶん、閉じ忘れの事故も起きません。依存モジュールを入れられない環境に当たったり、細かい制御が必要になったら SqlClient に移る、の順が学びやすいです。

Q2. UPDATE や INSERT も同じ書き方でできますか?

できます。Invoke-Sqlcmd-Query に UPDATE/INSERT を書けばそのまま実行されます。SqlClient は結果を読まない場合、ExecuteReader の代わりに ExecuteNonQuery() を使うと更新件数が返ります。どちらも、値のパラメータ化だけは忘れないでください。

Q3. トランザクションを使いたい場合は?

SqlClient(道B)を使ってください。$conn.BeginTransaction() でトランザクションを開始して、$cmd.Transaction に設定、成功なら Commit()、失敗なら Rollback() します。Invoke-Sqlcmd は単発実行向きで、複数文をまとめて巻き戻すような制御には向きません。

次に読むべき記事

以上!

同じ接続の閉じ忘れでプールを枯らした人いたら、どんどんシェア待ってるぜ!!


執筆者

バイブス父さん — 業務 SE 7 年 (SIer 正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SIer の正社員からフリーランスに転じ、 複数のエージェント経由で案件を回してきた経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る

この記事のコードと手順は ぜんぶ動作検証済み。 安心して現場で試してくれ。
バイブス父さん

現役の業務SE。C# / SQL Server 保守の現場から、コードも人もキャリアも全部書く。 実体験ベース。

運営者について