🐄

【Powershell/ActiveDirectory】所属するOUの階層の深さが違くてもスクリプトでユーザーを一括作成したい!

2025/02/04に公開

どうも!新人エンジニアの前歯すきっ歯です🦷

いつまでもGUIでポチポチして作成するわけにはいかないので、
PowershellでADユーザを大量作成することにしました!
自分で設定した課題ですが、初心者には大変すぎる課題でした笑

今回も頑張ったので努力が無駄にならぬように備忘録を付けておきます~~~!

1.本記事について

1.1💪勉強のゴール💪

今回のゴールはCSV内にある120人分のユーザー情報からOUの階層が異なる全ユーザーをスクリプトで一括作成することです!具体的には以下3点を行います。

1.2 前提条件

以下を4点を前提条件とします!

  1. gapteeth.studyというドメインでADサーバを作成済みです。
  2. WindowsSever2022のActiveDirectoryを利用しています。
  3. 以下画像のOUの構成を作成済みとします。
  4. OUの階層が上位の階層であるほど若い数字になるよう各OUの後に数字を振ります。

1.3 よくある記事との違い

OUの階層が異なる全ユーザを同時に作成する場合のスクリプトであることがよく出てくる記事との違いです。

1.4 読者に求める前提知識

IT初心者向けの記事ですが、ADサーバとPowerShellの基礎的な操作をしたことがある前提で記事を書きます。それらに触るのが初めての場合、もしよければ以下の記事を読んでみてください!自分の記事で恐縮ですが、初心者向けの勉強方法の記載をしてあります。

2.知識収集

僕みたいな初心者はいきなり「やってください」と言われましても難しいもので、
調べた時に分かりやすかったリンク9つをご紹介します。

2.1 Import-Csvコマンドについて

まずCSVを読み込むにあたり必要なImport-Csvコマンドについて丁寧に解説してくれていました。

2.2 連想配列(ハッシュテーブル)について

CSV内の各ドメインユーザーの情報は「A=100」のようにKey/Value形式で保管されています。Powershellではそれを連想配列と呼びます。その概念について理解しましょう!

2.3 連想配列をForeachでループする方法

ユーザー1人につき1つの連想配列があるので、それをユーザーの人数分処理する必要があります。連想配列をどのように繰り返し処理するか書いてくれています。

2.4 CSVファイル内の値を連想配列として格納する方法

では、上記で紹介したCSVファイルをPowershellスクリプト内で操作するためにどのように配列に格納するのか紹介してくれています。「3.2 データの配列への格納」の部分を参考にしました。

2.5 New-ADUserコマンドの解説

New-ADUserコマンドの使い方やよく使うオプションに関する解説、今回やりたいことの流れがざっと分かるかなと思います。

2.6 New-ADUserコマンドのオプションとGUIにおける対応付け

正直公式ナレッジを眺めたり、色んなサイトを見ていても「ところでそのオプションは何を表現しているの?」とずっと思っていました。以下サイトは有難いことに、ほぼ全てのオプションとGUI上の対応付けを一覧にしてくださっていて分かりやすいです。

2.7 New-ADUserコマンドのオプションでふりがなを指定する方法

ちょっとびっくりしたのですが、New-ADUserコマンドではドメインユーザーの全ての項目をオプションで網羅しておらず、Set-ADUserコマンドで補っている部分があります。New-ADUserコマンドのオプションでもふりがなを指定できますが、マイナーなオプションを指定する必要がありSet-ADUserコマンドの方が可読性が高そうなので、今回はSet-ADUSerコマンドを使います。

2.8 ユーザプリンシパル名(UPN)とsAMAAccountNameの違い

上記2つもよくつけられているオプションですがその違いが分からず調べてみました。

2.9 今回作成するスクリプトの模倣元

今回は以下サイトのスクリプトを大きな柱として作成しました。
こんなに素敵なスクリプトが世に出回っていて有難い…!

3.準備~練習用CSVファイルの作成~

3.1 練習用データの生成

100人以上のランダムな名前を作るのは骨が折れるので、練習データは以下リンクから作成しました!まずデータがないと勉強できませんからね~
https://testdata.userlocal.jp/
下記のように選択すると、最初から苗字と名前が別の列に出てくれて便利です◎
「生成」を押下するとCSVをダウンロードしてくれて、まぁ便利なこと!

▼選択項目

  • フォーマット:CSV
  • 行数:120行(120人分のデータを扱うから)
  • 氏名:漢字/ひらがな/ローマ字
  • 区切り:性と名を別々の項目として出力
    ※”区切りなし”を選択した意図は、この後Powershellで動かすにあたりスペースが名前の一部としてコマンドに認識されるのが面倒だと思ったからです。

    他の選択をすることでNew-ADUserコマンドの他オプションの勉強もできそうですね!

    ダウンロードしたファイル ※上記リンクから生成しているため実在する名前と関係ありません。

3.2 練習用データの編集

このままのCSVでは今回のゴールの項目ではないので、使えるようにちょっと編集します。

3.2.1 性と名を結合する(Ctrl+E)

CSVファイルの行を増やしてから、以下リンクの方法で性別と名前を結合しました。
https://canon.jp/biz/solution/smb/tips/excel/customer/03

まず列を追加して

2行目で結合の規則を作って

3行目でCtrl+Eを押せば結合完了!

3.2.2 1行目の項目と不要な部分の修正

今回使えるように「①項目の修正」と「②OUとパスワードの行の追加」を行います!

H列とI列を削除して

1行目の項目を修正して

OU、パスワードの行を追加します!

4.検証

4.1 疑問~PathオプションはOU内の項目が空でもよいのか?~

ふと思いました。「CSVファイル内のOUの値が空の場合、New-ADUserコマンドの-Pathオプションの挙動はどうなるのか?必ずOUの項目が存在する必要があるかないかでスクリプトの書き方が変わる…」

4.1.1 前提

OU1~OU3の項目がCSV内にありますが、ユーザー毎にOU1~OU3どの項目を持っているか(=所属先)が異なります
例)営業部直下("OU1=営業部")、営業部第1課配下("OU2=第1課,OU1=営業部")どちらにもユーザがいるCSVを作りました。すなわち、営業部直下("OU1=営業部")のユーザーはOU2とOU3の項目がCSV上空白です。また、第2課に所属するユーザ(“OU=営業部, OU=第2課“)はOU3の項目がCSV上空白です。

OUの構造

実際のCSVファイル ※右側のOU1やOU2の列に注目

4.1.2 疑問の具体例

営業部直下にユーザーを作りたいときに本来ならば-Path ”OU=営業部"ですが、-Path ”OU=,OU=営業部"という書き方でいいのか?…ということ。上記が許されるならばOUが存在する場合としない場合で条件分岐のスクリプトを書く必要がない。しかし、上記が許されない場合はOUが空の場合条件分岐させてスクリプトを書く必要がある。

4.2 検証

営業部直下にユーザーを作るべくOU=,OU=営業部を試しましたがエラーが出てしまいました。
では、条件分岐でコマンドを作りますか…

エラーの様子
#ADユーザー作成コマンド
New-ADUser
-name testuser
-AccountPassword (ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force) 
-UserPrincipalName testuser@gapteeth.study
-DisplayName "testuser"
-Enabled $true
-Path "OU=,OU=営業部,DC=gapteeth,DC=study"

#エラーメッセージ
New-ADUser : ディレクトリオブジェクトが見つかりません。
発生場所 行:2 文字:1
+ New-ADUser -name testuser -AccountPassword (ConvertTo-SecureString "P ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (CN=testuser,OU=...=gapteeth,DC=study:String) [New-ADUser], ADIdentityNotFoundException
    + FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Management.Commands.NewADUser
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

備考

2.3で紹介した以下記事の「トラブルシュート」にも値が空だと厳しそうな記載がありますね!
https://blog.jbs.co.jp/entry/2024/02/27/114753

5.作成したスクリプトと意図

5.1 作成したスクリプト

結論、以下が作成して実行の確認ができたスクリプトです。
スクリプトを細分化して作成の意図を後述していきます。

ドメインユーザー作成スクリプト
#エラーだったら実行をストップする
$ErrorActionPreference = "Stop"

#CSVファイルのインポート
$csvdata = Import-Csv -Path "C:\New-ADUser練習用.csv"
#ドメインユーザーの作成
foreach ($userdata in $csvdata){
    #New-ADUserコマンドの-Pathオプションに用いる$Pathを作成する。
    $Path = "" 
    #OU3~OU1の中身があるか確認する。
    for($i = 3;$i -ge 1;$i--){
        $OU = $userdata."OU${i}"
        if($OU -ne ""){
            $Path += "OU=${OU},"
        }       
    }
    #最後にドメインの部分を付け足す。
    $Path += "DC=gapteeth,DC=study"

    #各ユーザーの情報を連想配列にまとめる。
    $newUserParams = @{
        Name = $userdata.Name #ADツリー上の表示名
        Surname = $userdata.Surname #姓
        GivenName = $userdata.GivenName #名
        Path = $Path #ユーザーを作成するOU
        UserPrincipalName = $userdata.UserPrincipalName + "@gapteeth.study" #メールアドレス
        AccountPassword = ConvertTo-SecureString $userdata.password -AsPlainText -Force #パスワード
        Enabled = $True #ユーザーの有効化
        }

    #ドメインユーザーを作成する。
    New-ADUser @newUserParams

    #フリガナを追加する。
    #ふりがなの情報を格納する連想配列$rubyを作成する。
    $ruby = @{
        "msDS-PhoneticDisplayName" = $userdata.PhoneticDisplayName
        "msDS-PhoneticLastName" = $userdata.PhoneticLastName
        "msDS-PhoneticFirstName" = $userdata.PhoneticFirstName
    }

    #ふりがなの追加を実行
    Set-ADUser -Identity $userdata.Name -Add $ruby 
    }

初心者の書いたスクリプトなので拙い部分があると思います。より簡潔で可読性が高いコードを思いついた方はコメントで教えていただける嬉しいです!よろしくお願いします!

5.2 作成の意図

5.2.1 ErrorActionPreferenceコマンド

  • 個別のエラー1つずつに向き合うことができる
    スクリプト内で出た最初のエラーで実行を止めるコマンドです。これだけ長いスクリプトだと人数分(=120回)やfor文の繰り返しの回数だけエラーが出て大変です。どれだけスクロールしてもエラーで赤字まみれの画面からどこからどこまでがスクリプト1周分のエラーなのか見つける苦労がなくなり、簡単に個別のエラー1つずつに向き合うことができます!
$ErrorActionPreference
#エラーだったら実行をストップする
$ErrorActionPreference = "Stop"

5.2.2 Import-Csvコマンドとforeachコマンド

  • CSVファイル内のユーザー1人ずつforeach内のコマンドを実行する
    CSVファイルをインポートしてCSVファイルの全データが格納されている$csvdataから、foreachコマンドで1行ずつ(=ユーザー1人分のデータずつ)$userdataに渡します。その$userdataを基にしてforeach内のスクリプトが実行されます。
CSVファイルのインポート
#CSVファイルのインポート
$csvdata = Import-Csv -Path "C:\New-ADUser練習用.csv"
foreach ($userdata in $csvdata){
~~~~~中略~~~~
    }

5.2.3 $Pathの定義

  • Pathオプションの指定方法に合わせ、OU3(下位階層のOU)から順に$Pathに加えていく
    Pathオプションでは-Path "OU=採用Gr,OU=人事課,OU=総務部,DC=gapteeth,DC=studyというように、階層が深い順にOUを指定して最後にドメインをつけます。
    また、for文は「iは3からスタートしてiが1以上ならば1つずつ小さくしつづける」という条件文でを回しています。iの中身が3から1ずつ小さくなっていくので、$userdata."OU${i}"すなわち$OUOU3からOU1の順に中身が格納されます。
    さらにif($OU -ne "")により、$OUの中身があった場合は、$Pathの中身に従来のものに加えて$OUの中身が足される仕組みとなっています。
    そして、最後に$Path += "DC=gapteeth,DC=study"でドメインを足します。
for文の中身
    for($i = 3;$i -ge 1;$i--){
        $OU = $userdata."OU${i}"
        if($OU -ne ""){
            $Path += "OU=${OU},"
        }       
    }
    #最後にドメインの部分を付け足す。
    $Path += "DC=gapteeth,DC=study"
  • for文の前で$Path = ""を定義してユーザー毎に$Pathの中身を空にする。
    今回for文の中とfor文後のPathでは$Path+=~の記載により、$Pathの中身を変えるのではなく、従来の$Pathの後ろに新たな文字を付け加えています。ユーザー毎に$Pathの中身が異なるので、毎度$Pathをまっさらにできるように$Path = ""と書いています。

5.2.4 ユーザー情報の格納とユーザーの作成

  • 可読性を高めるためにユーザ情報を連想配列にまとめる
    4.2検証の部分で書いたようにNew-ADUserコマンドとオプションを並べて書いても問題はありません。個人的にはオプションが多く読みにくいと感じたので、連想配列内にユーザー情報をまとめています。
ユーザーの作成
    $newUserParams = @{
        Name = $userdata.Name #ADツリー上の表示名
        Surname = $userdata.Surname #姓
        GivenName = $userdata.GivenName #名
        Path = $Path #ユーザーを作成するOU
        UserPrincipalName = $userdata.UserPrincipalName + "@gapteeth.study" #メールアドレス
        AccountPassword = ConvertTo-SecureString $userdata.password -AsPlainText -Force #パスワード
        Enabled = $True #ユーザーの有効化
        }

    #ドメインユーザーを作成する。
    New-ADUser @newUserParams

5.2.5 ふりがなの設定

  • 可読性を高めるためにユーザ情報を連想配列にまとめる Part2
    同様の理由でフリガナ部分を連想配列にまとめています。また、ふりがな以外の設定のためにSet-ADUSerコマンドを使用することもあるかと考え、今後のこのスクリプトの使いやすさのためにNew-ADUserコマンドとは別の連想配列を作成しています。
ふりがなの設定
    #フリガナを追加する。
    #ふりがなの情報を格納する連想配列$rubyを作成する。
    $ruby = @{
        "msDS-PhoneticDisplayName" = $userdata.PhoneticDisplayName
        "msDS-PhoneticLastName" = $userdata.PhoneticLastName
        "msDS-PhoneticFirstName" = $userdata.PhoneticFirstName
    }

    #ふりがなの追加を実行
    Set-ADUser -Identity $userdata.Name -Add $ruby 

6.スクリプト作成中の備忘録

もちろん初心者がスラスラできるわけはなく、次回以降のために今回勉強したことを記載しておきます。基礎的でお恥ずかしいものばかりですが、ご容赦くだされ!

6.1 $nullは変数を空にするもの

プログラミング言語と同様に$nullは空を表すものかと思い、当初は$nullをif文の判定に入れていました。しかし、for文は回るがif文内のコマンドはなぜか処理されない事象に直面して$nullで判定させていることがおかしいことに気が付きました…。$nullは変数を空にするものなので、空自体を表すものではないのですね!(初心者過ぎる)

if文内での正しい判定方法
>#正しいスクリプト==================
>if($OU -ne ""){
>    $Path += "OU=${OU},"
>}    
<#誤ったスクリプト==================
<if($OU -ne $null){
<    $Path += "OU=${OU},"
<}    

6.2 連想配列のメソッドの記載は文字列で行う

連想配列のメソッドの呼び出しを$userdata.OU${i}と書いていました。しかし、以下のようなエラーが出ました。 ※当初$OU = $userdata."OU${i}"と置かずにif文の判定に入れていたので以下のようなエラーメッセージとなっています。

エラーコード
[System.Collections.Hashtable]'OU' という名前のメソッドが含まれないため、メソッドの呼び出しに失敗しました。
発生場所 C:\New-ADUserコマンド練習.ps1:30 文字:12
+         if($userdata.OU{$i} -ne ""){
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~
   + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
   + FullyQualifiedErrorId : MethodNotFound

連想配列のメソッドは文字列で入れる必要があるようで、メソッド部分に変数を入れる際は以下のように""でメソッド全体を囲み文字列とする必要があります。

連想配列のメソッド指定方法
>#正しいスクリプト==================
>$OU = $userdata."OU${i}"
<#誤ったスクリプト==================
<$OU = $userdata.OU${i}

6.3 Pathオプションを変数にするときはデフォルトで””が含まれる

$Pathが文字列である必要があるので、最初はOU名やドメイン名の前後に””を含むスクリプトで記述していました。しかし、デフォルトで””が含まれるらしくエラーが出てしまいました。NotSpecified: (CN=稲田奈緒子,"OU=社長,DC=gapteeth,DC=study":String)の部分を見ると、””を含むOU・ドメイン名で扱われていることが分かります。

エラーコード
New-ADUser : オブジェクト名の構文が間違っています。
    発生場所 C:\New-ADUserコマンド練習.ps1:27 文字:5
    +     New-ADUser @newUserParams
    +     ~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (CN=稲田奈緒子,"OU=社長,DC=gapteeth,DC=study":String) [New-ADUser], ADException
        + FullyQualifiedErrorId : ActiveDirectoryServer:8335,Microsoft.ActiveDirectory.Management.Commands.NewADUser

先述したスクリプトの通り$newUserParams内Pathオプションでは変数の$Pathを指定しているので、下記のように””を除いてPathを指定をすることで上手くいきました。

$Path指定部分抽出
>#正しいスクリプト==================
>$Path = ""
>~~~for文とif文中略~~~
>$Path += "DC=gapteeth,DC=study"
<#誤ったスクリプト==================
<$Path = """" #ダブルクオーテーションの中でダブルクオーテーションを書くときは2つ並べる。
<~~~for文とif文中略~~~
<$Path += "DC=gapteeth,DC=study"""

6.4 ハイフンが含まれるオプションは文字列で指定する。

$rubyのオプションを$newUserParamsのオプション同様に””で囲まずに指定したところ以下のエラーが出ました。

エラーコード
Set-ADUser : パラメーター名 'msDS-PhoneticLastName' に一致するパラメーターが見つかりません。
発生場所 C:\New-ADUserコマンド練習.ps1:41 文字:16
+     Set-ADUser @ruby
+                ~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Set-ADUser]、ParentContainsErrorRecordException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.ActiveDirectory.Management.Commands.SetADUser

ハイフンが含まれるのは多くの場合コマンドであるため、たとえオプションの名前が合っていたとしても””で囲まないとオプションとしてPowerShellに読み込んでもらえないみたいです。(例:New-ADUser/Set-ADUser)

Set-ADUserオプション指定部分
>#正しいスクリプト==================
>$ruby = @{
>        "msDS-PhoneticDisplayName" = $userdata.PhoneticDisplayName
>        "msDS-PhoneticLastName" = $userdata.PhoneticLastName
>        "msDS-PhoneticFirstName" = $userdata.PhoneticFirstName
>    }
<#誤ったスクリプト==================
<$ruby = @{
<        msDS-PhoneticDisplayName = $userdata.PhoneticDisplayName
<        msDS-PhoneticLastName = $userdata.PhoneticLastName
<        msDS-PhoneticFirstName = $userdata.PhoneticFirstName
<    }

6.5 Set-ADUserのIdentityではsAMAccountNameを指定する。

Set-ADUSerコマンドのIdentityオプションはいかにも$userdata.UserPrincipalNameだと思い込み指定したところエラーが出てしまいました…。

エラーの様子
#エラーメッセージ
 Set-ADUser : ID 'MoriAkihiro' のオブジェクトが 'DC=gapteeth,DC=study' で見つかりません。
    発生場所 C:\New-ADUserコマンド練習.ps1:42 文字:5
    +     Set-ADUser -Identity $userdata.UserPrincipalName -Add $ruby
    +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : ObjectNotFound: (MoriAkihiro:ADUser) [Set-ADUser], ADIdentityNotFoundException
        + FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException,Microsoft.ActiveDirectory.Managem
       ent.Commands.SetADUser

結論、New-ADUserコマンド実行時にsAMAccountNameで指定した値をSet-ADUSerコマンドのIdentityオプションで指定する必要があります。しかし、今回のようにNew-ADUserコマンド実行時にsAMAccountNameを明示的に指定していない場合、Nameオプションで指定した値がsAMAccountNameに入ります。したがって、Set-ADUSerコマンドのIdentityオプションには
New-ADUserコマンドのNameオプションと同じ$userdata.Nameを指定します。

Set-ADUSerコマンドIdentitオプション
>#正しいスクリプト==================
>Set-ADUser -Identity $userdata.Name -Add $ruby
<#誤ったスクリプト==================
<Set-ADUser -Identity $userdata.UserPrincipalName -Add $ruby

6.6 UPN値が一意でないエラー

「UPN値(=UserPrincipalName)が一意でない」と出てエラーが出た清水貴志さんがもう1人CSVファイル内にいるのかと半信半疑でCSVファイル内を検索したところ、漢字は違いますがアルファベットは同じ方がいました…。上記の通りランダムでCSVのデータは作成したのですが、そんなこともあるんですね~

エラーの様子
#エラーメッセージ
New-ADUser : 追加または変更のために指定された UPN 値がフォレスト全体で一意でないため、操作に失敗しました。
発生場所 C:\New-ADUserコマンド練習.ps1:30 文字:5
+     New-ADUser @newUserParams
+     ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (CN=清水貴志,OU=経理課,...pteeth,DC=study:String) [New-ADUser], ADException
    + FullyQualifiedErrorId : ActiveDirectoryServer:8648,Microsoft.ActiveDirectory.Management.Commands.NewADUser


エラーの時とは別の「ShimizuTakashi」さんがいらっしゃった。
https://learn.microsoft.com/ja-jp/windows-server/identity/ad-ds/manage/component-updates/spn-and-upn-uniqueness#new-user-creation-fails-if-upn-isnt-unique

7.おわりに

最後までご覧いただきありがとうございました!!新人エンジニアの勉強なので拙い部分が多くあると思うので、随時コメントでご指摘いただけると嬉しいです🙆‍♂️

本記事を前提として、さらにセキュリティグループを作成したり、共有フォルダをファイルサーバ上に作成しています!もし良かったらご覧ください🫡
https://zenn.dev/gapteeth/articles/9a79aed1267b05
https://zenn.dev/gapteeth/articles/9a79aed1267b05

Discussion