PowerShellでmsNPCallingStationID/msNPSavedCallingStationIDをCSV一括更新する方法(既存ADユーザー対応)

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 やカスタム属性に寄せる設計も検討してください。安全な設計と確実な更新手順をセットで整えるのが、長期運用では一番の近道です。

この記事を書いた人

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

コメント

コメントする

目次