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 の DisplayName | DisplayName |
| 証明書 | 発行先(Subject / IssuedTo) | X509Certificate2.Subject | IssuedTo |
| 証明書 | 発行者(Issuer / IssuedBy) | X509Certificate2.Issuer | IssuedBy |
| 証明書 | 有効期限(NotAfter) | X509Certificate2.NotAfter | NotAfter |
| 証明書 | シリアル番号 | X509Certificate2.SerialNumber | SerialNumber |
| 証明書 | 用途(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 の「発行済み証明書」タブの主要対象 |
| userSMIMECertificate | S/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 のイメージ(列の意味が分かると運用が速い)
実務で「何の列が何を意味するか」が共有されていないと、せっかくエクスポートしても使われません。運用で説明しやすいよう、列の意味を整理します。
| 列名 | 内容 | 監査・運用での使いどころ |
|---|---|---|
| DisplayName | ADユーザーの表示名 | 担当者が見て分かる名前でフィルタリングしやすい(ただし同名があり得る) |
| 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)を起点に、部署単位の証明書運用を見える化していきましょう。

コメント