Active DirectoryのOU配下ユーザー公開証明書(X.509)を一括エクスポートするPowerShell手順|userCertificateをCSV出力

Active Directory の特定 OU 配下にいるユーザーの「発行済み証明書(Published Certificates)」を、Excel で扱いやすい 1 本の CSV にまとめて棚卸ししたい――そんなときに役立つのが PowerShell による一括エクスポートです。本記事では userCertificate 属性から X.509 証明書を取り出し、期限(NotAfter)や用途(EKU)まで含めて一覧化する実務向けの手順を解説します。

目次

この記事で解決できること

Active Directory ユーザーの証明書運用では、次のような場面で「OU 単位の一括棚卸し」が必要になります。

  • 部署 OU ごとに、期限切れ・期限間近の証明書を洗い出したい
  • どのユーザーに、どの発行者(CA)から、どんな用途(EKU)の証明書が発行されているか確認したい
  • ADUC(Active Directory ユーザーとコンピューター)画面の「発行済み証明書」タブを手作業で開いて回るのをやめたい
  • 監査・更改のために、DisplayName 付きの一覧を CSV で提出したい

ゴールは、指定 OU 配下の全ユーザーについて、ユーザーに紐づく証明書を「証明書 1 枚 = CSV 1 行」で出力することです。

要件の整理(出力に入れる項目)

今回の要件を「AD から取り出すもの」と「証明書から取り出すもの」に分けると、設計がブレません。

分類欲しい情報どこから取るかCSV列の例
ADユーザー表示名(DisplayName)Get-ADUser の DisplayNameDisplayName
証明書発行先(Subject / IssuedTo)X509Certificate2.SubjectIssuedTo
証明書発行者(Issuer / IssuedBy)X509Certificate2.IssuerIssuedBy
証明書有効期限(NotAfter)X509Certificate2.NotAfterNotAfter
証明書シリアル番号X509Certificate2.SerialNumberSerialNumber
証明書用途(EKU)EnhancedKeyUsageList(FriendlyName 連結)IntendedUsage

「発行済み証明書」タブと userCertificate 属性の関係

ADUC の「発行済み証明書(Published Certificates)」タブに表示される証明書は、基本的にユーザーオブジェクトの userCertificate 属性(マルチ値)に DER バイナリとして格納されています。PowerShell でやるべきことはシンプルで、次の変換を繰り返すだけです。

  • AD の userCertificate(バイト配列)を取り出す
  • .NET の X509Certificate2 に変換する
  • Subject / Issuer / NotAfter / SerialNumber / EKU を取り出して CSV 行にする

なお、運用によっては S/MIME 用の証明書が userSMIMECertificate に入っているケースもあります(環境次第です)。「Published Certificates タブに見えているものを一覧化したい」目的であれば、まずは userCertificate を対象にするのが定石です。必要に応じて応用で userSMIMECertificate も追加できます(後述)。

属性名用途の例格納形式メモ
userCertificateユーザーに紐づく公開証明書(スマートカード/クライアント認証など)DER(バイト配列、複数可)ADUC の「発行済み証明書」タブの主要対象
userSMIMECertificateS/MIME(メール暗号化/署名)関連DER(バイト配列、複数可)環境により併用。監査目的なら同時出力すると整理が楽

事前準備(ここを満たせばスクリプトは動く)

  • ActiveDirectory モジュールが使えること(RSAT を導入した管理端末、またはサーバー上で実行)
  • 対象 OU 配下のユーザー属性(DisplayName / userCertificate)を読み取れる権限があること
  • OU の識別名(DN)を把握していること(例:OU=Sales,DC=contoso,DC=local)
  • CSV の出力先フォルダに書き込み権限があること

また、Excel で文字化けなく開く目的なら、エンコーディング(UTF-8)も意識しておくと後工程がラクになります。特に PowerShell 7 系を使う場合は「UTF-8 BOM 付き」を選ぶと Excel 側での取り込みが安定します(これも後述します)。

OU 配下の全ユーザー証明書を 1 つの CSV にまとめて出力する PowerShell

まずは要件を過不足なく満たす「基本形」です。指定 OU 配下の全ユーザーについて、userCertificate を展開し、証明書 1 枚ごとに 1 行として CSV に書き出します。

# 対象 OU(環境に合わせて変更)
$ouDN  = "OU=LAPS1,DC=b,DC=local"

# 出力ファイルパス(環境に合わせて変更)
$csvPath = "C:\certs\certs.csv"

# 出力先フォルダが存在しない場合は作成
$outDir = Split-Path -Parent $csvPath
if (-not (Test-Path $outDir)) {
    New-Item -Path $outDir -ItemType Directory -Force | Out-Null
}

# Excel での取り込みを安定させたい場合のエンコーディング選択
# Windows PowerShell 5.1: UTF8 は BOM 付き
# PowerShell 7+: utf8BOM を明示すると BOM 付き
$encoding = "UTF8"
if ($PSVersionTable.PSVersion.Major -ge 7) {
    $encoding = "utf8BOM"
}

Get-ADUser -SearchBase $ouDN -Filter * -Properties DisplayName, userCertificate |
    ForEach-Object {
        $user = $_

        # DisplayName が未設定のユーザーがいる場合に備えてフォールバック
        $display = if ([string]::IsNullOrWhiteSpace($user.DisplayName)) { $user.Name } else { $user.DisplayName }

        # userCertificate(複数証明書の可能性あり)を展開
        foreach ($bytes in @($user.userCertificate)) {

            # null/空配列を安全にスキップ
            if ($null -eq $bytes -or $bytes.Length -eq 0) { continue }

            # バイナリ → X509Certificate2 オブジェクトへ変換
            $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($bytes)

            # EKU を FriendlyName 優先で取り出し、1セルに連結
            $ekuText = (@($cert.EnhancedKeyUsageList | ForEach-Object {
                if ([string]::IsNullOrWhiteSpace($_.FriendlyName)) { $_.Value } else { $_.FriendlyName }
            })) -join "; "

            # CSV 出力用の 1 行オブジェクト
            [PSCustomObject]@{
                DisplayName   = $display
                IssuedTo      = $cert.Subject
                IssuedBy      = $cert.Issuer
                IntendedUsage = $ekuText
                NotBefore     = $cert.NotBefore
                NotAfter      = $cert.NotAfter
                SerialNumber  = $cert.SerialNumber
            }
        }
    } | Export-Csv -Path $csvPath -NoTypeInformation -Encoding $encoding

この形の良いところは、Export-Csv を最後に 1 回だけ置いている点です。ユーザー単位ではなく、OU 配下の全ユーザー・全証明書をパイプラインで集約して、1 本の CSV にまとめて出力できます。

出力される CSV のイメージ(列の意味が分かると運用が速い)

実務で「何の列が何を意味するか」が共有されていないと、せっかくエクスポートしても使われません。運用で説明しやすいよう、列の意味を整理します。

列名内容監査・運用での使いどころ
DisplayNameADユーザーの表示名担当者が見て分かる名前でフィルタリングしやすい(ただし同名があり得る)
IssuedTo証明書 Subject(発行先)CN/UPN/SAN 等の手がかり。どの識別子向けの証明書かを判別
IssuedBy証明書 Issuer(発行者)CA 切替や中間 CA の混在を把握。想定外の発行元の検知にも使える
IntendedUsage拡張キー使用法(EKU)の FriendlyName 連結「クライアント認証」「スマートカードログオン」など用途別に棚卸し
NotBefore有効開始日更改タイミングの把握、重複発行(短期間に多発)の気づき
NotAfter有効期限(重要)期限切れの抽出、期限間近(例:30日以内)抽出の中心列
SerialNumberシリアル番号CA 側ログや失効情報(CRL/OCSP)と突き合わせるときのキー

補足として、DisplayName は便利ですが一意でないことがあります。現場で「誰の証明書か」を確実に突き止めたいなら、応用として SamAccountName や UserPrincipalName を列追加するのがおすすめです(後述のカスタム例を参考にしてください)。

ポイント解説(なぜこの書き方が安定するのか)

OU 配下だけを対象にする(SearchBase)

-SearchBase で OU の DN を指定すると、その OU 配下のみが対象になります。ドメイン全体を走査しないので、実行時間と負荷をコントロールしやすく、部署単位の棚卸しにも向いています。

userCertificate は「複数入る」前提で展開する

userCertificate はマルチ値属性のため、ユーザー 1 人に複数の証明書が入っていることがあります。よくあるケースは次のとおりです。

  • 過去の証明書が残っている(失効/期限切れ/置き換え)
  • 用途が異なる証明書が複数ある(例:クライアント認証と S/MIME)
  • 更改の過渡期で、新旧が一時的に併存している

このため、ユーザーごとに 1 行ではなく、証明書ごとに 1 行にしておくと後からの分析が圧倒的に楽になります。

バイト配列 → X509Certificate2 に変換してから情報を読む

userCertificate は「証明書そのもの(DER)」が入っているだけなので、文字列として直接扱うのは現実的ではありません。X509Certificate2 に変換すれば、Subject/Issuer/NotAfter のようなプロパティをそのまま参照できます。

EKU の出力は「FriendlyName が空のときもある」前提にする

環境や証明書の種類によっては、EKU の表示名(FriendlyName)が空になることがあります。上のスクリプトでは、FriendlyName が空のときに OID 値へフォールバックするため、用途が欠落しづらくなります。

CSV は最後に一括 Export(ファイルを分断しない)

ユーザー単位で Export-Csv すると、ファイルが増えて管理が破綻しがちです。「OU 単位で 1 ファイル」の要件なら、パイプラインの最後で Export-Csv を 1 回が最も扱いやすい形です。

Excel での扱いやすさを上げる工夫(そのまま開くより、取り込みが確実)

Excel は環境によって、CSV の文字コードや日付解釈がブレることがあります。より確実に扱うなら次の運用がおすすめです。

  • Excel の「データ」→「テキスト/CSV から」で取り込み、文字コード(UTF-8)を確認する
  • NotAfter 列にフィルタを付け、「今日より前」「30日以内」などで絞り込む
  • 用途(IntendedUsage)でフィルタし、「スマートカードログオン」「クライアント認証」など用途別に集計する

また、現場でよくある “見落とし” は「期限切れ(NotAfter が過去)なのに残っている証明書」です。期限切れは即障害とは限りませんが、棚卸し対象としては重要です。次のフィルタ例がすぐに役立ちます。

実務でよく使うフィルタリング例(期限切れ・期限間近・用途別)

期限切れ(NotAfter が過去)の証明書だけに絞る

Import-Csv "C:\certs\certs.csv" |
  Where-Object { [datetime]$_.NotAfter -lt (Get-Date) } |
  Export-Csv "C:\certs\certs_expired.csv" -NoTypeInformation -Encoding UTF8

期限が 30 日以内に迫っている証明書を抽出する

$limit = (Get-Date).AddDays(30)
Import-Csv "C:\certs\certs.csv" |
  Where-Object { [datetime]$_.NotAfter -lt $limit } |
  Export-Csv "C:\certs\certs_expiring_30days.csv" -NoTypeInformation -Encoding UTF8

用途(EKU)に「スマートカードログオン」を含むものだけ抽出する

Import-Csv "C:\certs\certs.csv" |
  Where-Object { $_.IntendedUsage -match "スマートカード" } |
  Export-Csv "C:\certs\certs_smartcard.csv" -NoTypeInformation -Encoding UTF8

ポイントは、まずは “全量を 1 本の CSV” にしておき、後工程で「用途別」「期限別」に分割する運用です。スクリプトを何本も作るより、メンテナンスが簡単で、監査要求の変更にも追従しやすくなります。

応用:運用で困りがちな点を最初から潰す(識別子列の追加・エラー耐性)

DisplayName だけだと同名ユーザーが存在した場合に事故りやすいので、実務では SamAccountName や UserPrincipalName を一緒に出すことが多いです。また、稀に userCertificate に壊れたデータが入っていると変換で例外になります。監査を止めないために try/catch を入れ、エラーも 1 行として記録しておくと原因追跡が容易です。

$ouDN  = "OU=LAPS1,DC=b,DC=local"
$csvPath = "C:\certs\certs_with_ids.csv"

$outDir = Split-Path -Parent $csvPath
if (-not (Test-Path $outDir)) { New-Item -Path $outDir -ItemType Directory -Force | Out-Null }

$encoding = "UTF8"
if ($PSVersionTable.PSVersion.Major -ge 7) { $encoding = "utf8BOM" }

Get-ADUser -SearchBase $ouDN -Filter * -Properties DisplayName, SamAccountName, UserPrincipalName, userCertificate |
ForEach-Object {
    $user = $_
    $display = if ([string]::IsNullOrWhiteSpace($user.DisplayName)) { $user.Name } else { $user.DisplayName }

    foreach ($bytes in @($user.userCertificate)) {
        if ($null -eq $bytes -or $bytes.Length -eq 0) { continue }

        try {
            $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes)

            $ekuText = (@($cert.EnhancedKeyUsageList | ForEach-Object {
                if ([string]::IsNullOrWhiteSpace($_.FriendlyName)) { $_.Value } else { $_.FriendlyName }
            })) -join "; "

            [PSCustomObject]@{
                DisplayName        = $display
                SamAccountName     = $user.SamAccountName
                UserPrincipalName  = $user.UserPrincipalName
                IssuedTo           = $cert.Subject
                IssuedBy           = $cert.Issuer
                IntendedUsage      = $ekuText
                NotBefore          = $cert.NotBefore
                NotAfter           = $cert.NotAfter
                SerialNumber       = $cert.SerialNumber
            }
        }
        catch {
            [PSCustomObject]@{
                DisplayName        = $display
                SamAccountName     = $user.SamAccountName
                UserPrincipalName  = $user.UserPrincipalName
                IssuedTo           = ""
                IssuedBy           = ""
                IntendedUsage      = ""
                NotBefore          = ""
                NotAfter           = ""
                SerialNumber       = ""
                Error              = $_.Exception.Message
            }
        }
    }
} | Export-Csv -Path $csvPath -NoTypeInformation -Encoding $encoding

この形にしておくと、同名ユーザーがいても識別でき、例外で処理が止まることも避けられます。監査・棚卸し用途では「止まらない」「追跡できる」が強いです。

応用:userSMIMECertificate も含めて出したい場合

もし環境的に S/MIME 証明書も合わせて棚卸ししたいなら、取得属性と展開対象を追加します。どの属性由来の証明書かを区別できるよう、SourceAttribute 列を入れると後から混乱しません。

$ouDN  = "OU=LAPS1,DC=b,DC=local"
$csvPath = "C:\certs\certs_all_attrs.csv"

$encoding = "UTF8"
if ($PSVersionTable.PSVersion.Major -ge 7) { $encoding = "utf8BOM" }

Get-ADUser -SearchBase $ouDN -Filter * -Properties DisplayName, userCertificate, userSMIMECertificate |
ForEach-Object {
    $user = $_
    $display = if ([string]::IsNullOrWhiteSpace($user.DisplayName)) { $user.Name } else { $user.DisplayName }

    $pairs = @(
        @{ Attr = "userCertificate";       Values = @($user.userCertificate) }
        @{ Attr = "userSMIMECertificate";  Values = @($user.userSMIMECertificate) }
    )

    foreach ($p in $pairs) {
        foreach ($bytes in $p.Values) {
            if ($null -eq $bytes -or $bytes.Length -eq 0) { continue }

            $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes)

            $ekuText = (@($cert.EnhancedKeyUsageList | ForEach-Object {
                if ([string]::IsNullOrWhiteSpace($_.FriendlyName)) { $_.Value } else { $_.FriendlyName }
            })) -join "; "

            [PSCustomObject]@{
                DisplayName     = $display
                SourceAttribute = $p.Attr
                IssuedTo        = $cert.Subject
                IssuedBy        = $cert.Issuer
                IntendedUsage   = $ekuText
                NotAfter        = $cert.NotAfter
                SerialNumber    = $cert.SerialNumber
            }
        }
    }
} | Export-Csv -Path $csvPath -NoTypeInformation -Encoding $encoding

「Published Certificates タブに出ているもの」だけが目的なら userCertificate で十分ですが、メール暗号化/署名の監査や証明書更改の棚卸しでは userSMIMECertificate も対象になることがあります。要件に合わせて切り替えてください。

大規模環境でのパフォーマンスと負荷対策

ユーザー数・証明書数が多い環境では、単純に全ユーザーを取得すると時間がかかります。次の工夫で負荷を下げられます。

工夫狙い具体例
証明書を持つユーザーだけ取得無駄なユーザー走査を減らすGet-ADUser に -LDAPFilter “(userCertificate=*)” を使う
対象 OU を小さく分ける1 回の実行サイズをコントロール部署 OU ごとに実行し、ファイルを分ける
Excel の行数上限に注意出力後に開けない事故を防ぐ証明書行が 100 万行を超えそうなら分割運用
実行場所を見直すネットワーク越しの遅延や制限を回避管理サーバーや DC 近傍で実行する

特におすすめなのが、-Filter * の代わりに -LDAPFilter を使って「証明書を持つユーザーだけ」を拾う方法です。次のように置き換えられます。

Get-ADUser -SearchBase $ouDN -LDAPFilter "(userCertificate=*)" -Properties DisplayName, userCertificate

これだけで、証明書を持たないユーザーが多数いる OU では体感が大きく変わります。

よくあるエラーと対処(止まらずに一覧化するためのチェック表)

症状原因の例対処
Get-ADUser が使えないActiveDirectory モジュール未導入 / RSAT 未導入RSAT を導入し、Import-Module ActiveDirectory を確認
OU DN が正しくなく 0 件になるSearchBase の DN 指定ミスADUC で OU のプロパティから識別名を確認して貼り付け
証明書変換で例外が出て止まる属性に壊れたデータ / 例外が未処理try/catch を入れて Error 列に記録(応用スクリプト参照)
IntendedUsage が空になるEKU を持たない証明書 / FriendlyName が空FriendlyName 空なら OID 値にフォールバックする
Excel で文字化けするUTF-8 の解釈がズレる(環境差)PowerShell 7+ では utf8BOM を指定し、Excel は「テキスト/CSV から」で取り込み

セキュリティと運用上の注意点

  • エクスポートされるのは公開証明書です。通常、秘密鍵そのものは AD の userCertificate には格納されません。ただし、証明書の情報自体が個人情報や内部情報に当たる場合があるため、CSV の保管場所・共有範囲は統制してください。
  • 監査目的なら、CSV をメール添付でばらまくより、アクセス制御された共有領域に置き、必要者のみ参照にする運用が安全です。
  • DisplayName は分かりやすい一方で、同名があり得ます。業務で確実性が必要なら SamAccountName/UPN を併記してください。
  • 「期限切れがある=即障害」とは限りませんが、棚卸しの観点では重要です。期限切れが多数残っている場合は、証明書更新の運用(自動登録、失効、残骸削除)の見直し材料になります。

まとめ

Active Directory の OU 配下ユーザーが持つ公開証明書(X.509)を一括で棚卸しするには、userCertificate 属性のバイナリを X509Certificate2 に変換し、必要な項目を CSV に吐き出すのが最短ルートです。Export-Csv を最後に 1 回だけ置く構成にしておけば、「全ユーザー分の全証明書を 1 ファイル」にまとめられ、Excel でのフィルタや集計もスムーズになります。期限切れ(NotAfter)や用途(EKU)を起点に、部署単位の証明書運用を見える化していきましょう。

この記事を書いた人

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

コメント

コメントする

目次