みなさんこんにちは!ヒロポンです!
運用スクリプトから 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 — 依存なしで細かく制御
.NET の SqlConnection / 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-Sqlcmd は SqlServer モジュール(または旧 SQLPS)に入っているコマンドです。モジュールが無い環境では、そもそも動きません。
# 入ってなければインストール(管理者権限・オンライン環境)
Install-Module -Name SqlServer -Scope AllUsers
閉じたイントラや、勝手にモジュールを入れられない現場だと、ここがネックになります。そういう時は、モジュール不要な道B(SqlClient)に寄せるのが確実です。
罠②: SqlClient は接続を閉じないと、プールが枯れる
俺が本番でやらかしたのがこれ。SqlConnection.Open() したのに Close() を忘れると、接続が返却されず、接続プールが枯渇します。
こうなると、他の処理も「接続が取れない」で軒並み止まる。たった1つの閉じ忘れが、システム全体を巻き込む事故になるわけです。
対策は、必ず try / finally の finally で Close() する。途中でエラーが飛んでも確実に閉じるためです。
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 の
SqlParameter(AddWithValue)は「真のパラメータ化」。値は 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-Sqlcmd や SqlDataAdapter で数百万行を一括取得すると、全部メモリに載って PowerShell が一気に重くなります。
大量データを流すなら、道B の SqlDataReader で1行ずつ読んで処理する(前ページの while ($reader.Read()))のが軽い。取得しながら順に捌けば、全行をメモリに抱えずに済みます。
まとめ・チートシート
2つの道を1枚にまとめると、こうです。

判断はシンプルで、信頼できる値の手早い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 で経歴詳細を見る



