Active Directory の既存ユーザーに、CSV で管理している MAC アドレスを反映したい――そんなときに候補に挙がるのが msNPCallingStationID / msNPSavedCallingStationID です。ただし ADSI での更新はハマりやすく、属性自体も「直接変更しない」注意点があります。本記事では、安全な設計の考え方と、PowerShell の Set-ADUser -Replace で確実に一括更新する手順をまとめます。
先に押さえるポイント(結論)
既存の Active Directory(AD)ユーザーに対して、CSV で管理している MAC アドレスを msNPCallingStationID / msNPSavedCallingStationID に一括反映したい場合、実務上は ActiveDirectory モジュール(RSAT)の Set-ADUser -Replace を使うのが最も確実です。ADSI([ADSI] / .Put() / .SetInfo())で更新できるケースもありますが、既存ユーザー更新で「エラーが出る」「反映されない」パターンが多く、トラブルシュートのコストが跳ね上がりがちです。
ただし、ここで扱う 2 つの属性は “内部利用” を前提とした性格が強く、スキーマや管理ツール側に「直接変更しない」旨の注意書きが付いていることがあります。運用としては、MAC アドレスを業務データとして保持したいなら extensionAttribute(例:extensionAttribute1)や独自属性 に保存する設計の方が安全です。どうしても msNPCallingStationID を使わざるを得ない場合にだけ、慎重に更新してください。
msNPCallingStationID / msNPSavedCallingStationID とは
msNPCallingStationID / msNPSavedCallingStationID は、NPS(Network Policy Server)/ IAS 系の機能と結びつきが深い属性で、RADIUS の “Calling-Station-Id” など、接続元を識別する値(環境によっては MAC アドレス相当)を保持する用途で登場します。一般的には次のような場面で話題になりがちです。
- 無線 LAN / 有線 802.1X の運用で、端末の MAC アドレスをユーザーに紐付けて管理したい
- NPS 側の条件(Calling-Station-Id)に合わせて、AD ユーザー側にも値を持たせたい
- 既存の台帳(CSV)に MAC アドレスがあり、AD へ取り込みたい
一方で、AD の属性には「アプリケーションが利用する内部属性」「UI から設定することが前提の属性」が存在します。これらをスクリプトで直接書き換えると、将来の動作差分や、管理ツールによる上書き、監査・運用上の想定外を招きやすいのが実情です。
“MAC アドレスを管理したい” のに、この属性を使うべきか
目的が “MAC アドレスを保管して参照したい” だけなら、内部属性に寄せるより 運用に耐える保存先 を選んだ方が長期的に安定します。
| やりたいこと | 推奨の保存先 | 理由(運用面) |
|---|---|---|
| MAC アドレスを台帳として保持し、検索・監査したい | extensionAttribute(例:extensionAttribute1〜15)/ カスタム属性 | 意味づけを自分たちで定義でき、将来の仕様変更に引きずられにくい |
| NPS/IAS の挙動に合わせて、特定の属性に値が必要 | msNPCallingStationID / msNPSavedCallingStationID | 目的が明確な場合に限り選択肢。ただし直接更新は慎重に |
| 1ユーザーに複数 MAC を持たせたい | カスタム属性(複数値)または extensionAttribute+区切り文字運用 | 運用ルールを明確化しやすい(例:CSV/CMDB と連携) |
ADSI で既存ユーザー更新がうまくいかない典型パターン
質問のように [ADSI] でバインドして .Put() → .SetInfo() の流れを組んでも、既存ユーザーでは失敗することがあります。原因は 1 つではなく、複数の要因が重なりがちです。ここでは現場で遭遇しやすいポイントを整理します。
属性が「単一値」ではなく「複数値」扱いになっている
環境やスキーマ定義によっては、msNPCallingStationID が複数値(multi-valued)として扱われます。ADSI の .Put() に単一文字列を渡すと通るケースもありますが、既に複数値が入っているユーザーに対して “置換” のつもりが “型不一致” になり、反映されないことがあります。
PowerShell の AD コマンドレットは、配列(@(...))を渡す運用に寄せやすく、スキーマの差分を吸収しやすいのが利点です。
権限・ACL の問題(委任が効いていない/AdminSDHolder の影響)
既存ユーザーでも「特定のユーザーだけ」書き換えが失敗する場合、ACL(アクセス制御) が疑わしいです。たとえば対象ユーザーが保護グループ(Domain Admins など)に属していると、AdminSDHolder の影響で委任が剥がれ、スクリプト実行者が書き込み権限を持たないことがあります。
- 一部のユーザーだけ更新できない
- OU で委任したはずなのに、属性だけ書けない
この場合は「スクリプトの書き方」よりも、権限設計・委任範囲 を見直す方が近道です。
バインド先が間違っている(検索結果の DN がずれている)
CSV に sAMAccountName だけを書いて、そこから DN を組み立てていると、OU 移動や同名ユーザーが混じったタイミングで、別オブジェクトを掴むことがあります。ADSI は “掴んだオブジェクトに対しては黙って失敗する” ように見えるケースもあり、ログが薄いと原因究明が難しくなります。
Set-ADUser -Identity は sAMAccountName / DN / GUID など複数の識別子に対応し、-ErrorAction Stop と組み合わせると失敗を確実に捕捉できます。
値の形式が想定と違う(MAC の表記ゆれ、空白、区切り文字)
Calling-Station-Id が MAC アドレスとして来る環境でも、表記は統一されていないことが多いです。
AA-BB-CC-DD-EE-FF(ハイフン)AA:BB:CC:DD:EE:FF(コロン)AABBCCDDEEFF(区切り無し)aa-bb-cc-dd-ee-ff(小文字)
CSV 上の表記ゆれをそのまま入れると、後工程(NPS ポリシー、検索、監査)が破綻します。スクリプト側で正規化するのが現実的です。
そもそも「直接変更すべきでない」属性である
最も重要なのはここです。これらの属性は “便利だから台帳に使う” というより、特定機能のために存在する属性 です。直接変更が禁止というより「責任を持って運用できるか」を問われる領域なので、組織内の設計方針として “使う/使わない” を先に決めておくと事故が減ります。
推奨:Set-ADUser -Replace で CSV から一括更新する
ここからは「それでも更新する」前提で、既存ユーザーに対して確実に反映させる方法を紹介します。ポイントは次の 3 つです。
- ActiveDirectory モジュール(RSAT)を利用し、LDAP 更新をコマンドレットに任せる
- CSV の値を 正規化(フォーマット統一) してから投入する
- 検証(Get-ADUser)とログ出力 をセットで実施する
事前準備
- 管理端末またはサーバーに RSAT(ActiveDirectory モジュール)を用意
- 対象 OU/ユーザーに対して、スクリプト実行者が属性を書き込める権限を持つ
- 実行前にテスト OU で試し、ロールバック方針(戻す CSV やバックアップ)を用意
CSV の例(最低限)
まずは sAMAccountName と MAC を 1 つずつ持つシンプルな形式が扱いやすいです。
SamAccountName,MacAddress
taro.yamada,AA-BB-CC-DD-EE-FF
hanako.suzuki,11-22-33-44-55-66
複数 MAC を持たせたい場合は、列を増やすか、区切り文字で持たせます(例:セミコロン区切り)。
SamAccountName,MacAddresses
taro.yamada,AA-BB-CC-DD-EE-FF;11-22-33-44-55-66
更新の基本形(既存ユーザーを置換)
質問にある “既存ユーザー更新” で最も再現性が高いのが、Set-ADUser -Replace に寄せる方法です。-Replace は指定した属性の値を “入れ替え” ます。
Import-Module ActiveDirectory
Import-Csv .\mac.csv | ForEach-Object {
$sam = $*.SamAccountName
$mac = $*.MacAddress
Set-ADUser -Identity $sam -Replace @{
msNPCallingStationID = $mac
msNPSavedCallingStationID = $mac
}
}
ネット上の例では、New-ADUser -PassThru でユーザーを新規作成した直後に Set-ADUser -Replace を流し込む流れ(作成→属性反映)が紹介されることがあります。しかし今回の主旨が「既存ユーザーの更新」なら、New-ADUser は不要です。-Identity に sAMAccountName(または DN/GUID)を指定して、Set-ADUser -Replace だけで更新できます。
この最小例が動くなら、次のステップとして “正規化・ログ・例外処理” を足して、運用品質を上げていきます。
実運用向け:MAC 正規化+空欄ならクリア+ログ出力(推奨)
MAC の表記を揃え、CSV が空欄なら値を削除(クリア)し、結果をログに残す例です。-WhatIf を付ければ “実際には変更しない” ドライランにも使えます。
Import-Module ActiveDirectory
$CsvPath = ".\mac.csv"
$LogPath = ".\update-msnp-mac.log"
function Normalize-MacAddress {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return $null }
# 区切り文字を除去して 12 桁の16進に寄せる
$hex = ($Value -replace "[:-]", "" -replace "\s", "").ToUpper()
if ($hex -notmatch "^[0-9A-F]{12}$") { return $null }
# ハイフン区切り(AA-BB-...)に統一
return ($hex -replace "(.{2})(?=.)", '$1-')
}
"Start: $(Get-Date -Format o)" | Out-File -FilePath $LogPath -Encoding utf8
Import-Csv $CsvPath | ForEach-Object {
$sam = $*.SamAccountName
$raw = $*.MacAddress
$mac = Normalize-MacAddress $raw
try {
if ($null -eq $mac) {
# 空欄・不正値は「クリア」扱い(運用方針に合わせて変更)
Set-ADUser -Identity $sam -Clear msNPCallingStationID, msNPSavedCallingStationID -ErrorAction Stop
"[$sam] Clear (raw='$raw')" | Out-File -FilePath $LogPath -Append -Encoding utf8
} else {
Set-ADUser -Identity $sam -Replace @{
msNPCallingStationID = $mac
msNPSavedCallingStationID = $mac
} -ErrorAction Stop
"[$sam] Set '$mac' (raw='$raw')" | Out-File -FilePath $LogPath -Append -Encoding utf8
}
}
catch {
"[$sam] ERROR: $($_.Exception.Message) (raw='$raw')" | Out-File -FilePath $LogPath -Append -Encoding utf8
}
}
"End: $(Get-Date -Format o)" | Out-File -FilePath $LogPath -Append -Encoding utf8
不正な MAC 文字列を “NULL → クリア” にしているのは、事故を避けるための例です。もし「不正値はエラーで止めたい」なら、Normalize-MacAddress が $null を返したときに例外を投げるように作り替えてください。
複数 MAC を 1 ユーザーに入れたい場合(複数値対応)
スキーマが複数値である前提、または運用として複数 MAC を許可する前提の場合、配列を渡します。CSV をセミコロン区切りで持たせる例です。
Import-Module ActiveDirectory
function Normalize-MacList {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return @() }
$items = $Value -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
$normalized = foreach ($i in $items) {
$m = ($i -replace "[:-]", "" -replace "\s", "").ToUpper()
if ($m -match "^[0-9A-F]{12}$") { ($m -replace "(.{2})(?=.)", '$1-') }
}
# 重複排除
return $normalized | Sort-Object -Unique
}
Import-Csv .\mac.csv | ForEach-Object {
$sam = $*.SamAccountName
$macs = Normalize-MacList $*.MacAddresses
if ($macs.Count -eq 0) {
Set-ADUser -Identity $sam -Clear msNPCallingStationID, msNPSavedCallingStationID
} else {
Set-ADUser -Identity $sam -Replace @{
msNPCallingStationID = $macs
msNPSavedCallingStationID = $macs
}
}
}
注意点として、複数値にした場合は “どの MAC がどの端末か” の台帳管理が別途必要になります。例えば、端末名・資産番号・利用者・最終確認日など、監査に耐える情報を別テーブル(CMDB)で持つ方が運用は安定します。
-Replace / -Add / -Remove / -Clear の使い分け
更新が “効かない” と感じる多くのケースは、目的に合わない更新方式を選んでいることが原因です。PowerShell で整理すると次の通りです。
| 操作 | 用途 | 特徴 | 例 |
|---|---|---|---|
| -Replace | 値を入れ替える(上書き) | 最も分かりやすい。単一値・複数値どちらも扱いやすい | Set-ADUser -Replace @{attr=value} |
| -Add | 既存の値に追加する | 複数値属性で「追記」したいとき向き | Set-ADUser -Add @{attr=value} |
| -Remove | 指定値だけ削除する | 複数値属性から特定値を抜きたいとき向き | Set-ADUser -Remove @{attr=value} |
| -Clear | 属性そのものを空にする | “空欄=未登録” の運用に合う。$null 代入より安全 | Set-ADUser -Clear attr1,attr2 |
更新後の確認方法(反映チェック)
スクリプトが正常終了しても、確認をしないと「実は別の値が入っていた」「複数値のつもりが単一値になっていた」などの見落としが起きます。最低限、次の 2 段構えで確認してください。
PowerShell で属性値を直接見る
Get-ADUser -Identity taro.yamada -Properties msNPCallingStationID,msNPSavedCallingStationID |
Select-Object SamAccountName,msNPCallingStationID,msNPSavedCallingStationID
管理ツール側(ADUC/ADSIEdit)で見る
- ADUC(Active Directory ユーザーとコンピューター)で “Dial-in” 系の項目に紐づく UI がある場合は、そちらでも確認
- UI に出ない・見え方が怪しい場合は ADSIEdit で属性を直接確認(読み取り専用のつもりで扱う)
環境やツールのバージョンによって、UI の表示名と属性名が一致しないケースがあります。最終的には Get-ADUser -Properties で確認するのが確実です。
うまく更新できないときのチェックリスト
| 症状 | よくある原因 | 確認・対策 |
|---|---|---|
| エラーが出ずに反映されない | 別オブジェクトを掴んでいる/ログがない | Get-ADUser で対象ユーザーを明示的に取得し、DN/GUID をログに残す |
| 特定ユーザーだけ失敗する | ACL/AdminSDHolder/委任不足 | 対象ユーザーの所属グループと権限を確認。OU 委任の対象外になっていないかもチェック |
| 不正な形式だと怒られる | MAC の表記ゆれ、空白、区切り文字 | スクリプト側で正規化(12桁16進チェック)し、通らない値はエラーまたはクリア扱いにする |
| 複数値を入れたいのに上書きされる | -Replace を理解していない/CSV 設計が単一値 | 複数値運用なら配列で渡す。追記なら -Add、特定削除なら -Remove |
| 更新直後に戻ってしまう | 別の仕組み(管理ツール・同期・プロビジョニング)が上書き | 人手更新・自動同期の“正”を決め、片方を無効化。運用設計を統一する |
運用の落とし穴と、設計のおすすめ
最後に、現場で事故が起きやすいポイントをまとめます。技術的に更新できる ことと、運用として正しい ことは別です。
- 属性の意味づけをドキュメント化:何を入れるのか(端末 MAC / AP MAC / どのフォーマット)を明文化
- 更新の単一化:手作業、CSV、ID 管理ツールなど更新経路が複数あると必ず齟齬が出る
- 監査項目を別に持つ:最終確認日、登録者、根拠チケットなどは別ログ(CMDB/台帳)で管理
- 将来の移行を見据える:将来 extensionAttribute に移す可能性があるなら、今から値の形式を揃えておく
どうしても msNP* を使うなら
どうしても msNPCallingStationID / msNPSavedCallingStationID を更新する必要がある場合は、次のガードレールを推奨します。
- 本番投入前にテスト OU で検証し、値の反映・NPS 動作・ログイン影響を確認
- スクリプトは
-WhatIfでドライランできるように作る - 投入前後で “戻せる CSV” を必ず用意し、ロールバック手順を文章化
- 更新結果(成功/失敗)をログに残し、監査に耐える形にする
まとめ
既存 AD ユーザーに CSV から MAC アドレス相当の値を反映する場合、ADSI での直接更新はハマりどころが多く、まずは Set-ADUser -Replace を軸に組むのが現実的です。一方で、msNPCallingStationID / msNPSavedCallingStationID は内部利用の色が濃い属性なので、MAC 管理が主目的なら extensionAttribute やカスタム属性に寄せる設計も検討してください。安全な設計と確実な更新手順をセットで整えるのが、長期運用では一番の近道です。

コメント