【Powershell/ActiveDirectory】所属するOUの階層の深さが違くてもスクリプトでユーザーを一括作成したい!
どうも!新人エンジニアの前歯すきっ歯です🦷
いつまでもGUIでポチポチして作成するわけにはいかないので、
PowershellでADユーザを大量作成することにしました!
自分で設定した課題ですが、初心者には大変すぎる課題でした笑
今回も頑張ったので努力が無駄にならぬように備忘録を付けておきます~~~!
1.本記事について
1.1💪勉強のゴール💪
今回のゴールはCSV内にある120人分のユーザー情報からOUの階層が異なる全ユーザーをスクリプトで一括作成することです!具体的には以下3点を行います。
1.2 前提条件
以下を4点を前提条件とします!
- gapteeth.studyというドメインでADサーバを作成済みです。
- WindowsSever2022のActiveDirectoryを利用しています。
- 以下画像のOUの構成を作成済みとします。
- OUの階層が上位の階層であるほど若い数字になるよう各OUの後に数字を振ります。
1.3 よくある記事との違い
OUの階層が異なる全ユーザを同時に作成する場合のスクリプトであることがよく出てくる記事との違いです。
1.4 読者に求める前提知識
IT初心者向けの記事ですが、ADサーバとPowerShellの基礎的な操作をしたことがある前提で記事を書きます。それらに触るのが初めての場合、もしよければ以下の記事を読んでみてください!自分の記事で恐縮ですが、初心者向けの勉強方法の記載をしてあります。
-
ADサーバについて
https://zenn.dev/gapteeth/articles/bd3053f8a6cebd -
PowerShellについて ※共有フォルダに関する記載もあり
https://zenn.dev/gapteeth/articles/45338e99347e14
2.知識収集
僕みたいな初心者はいきなり「やってください」と言われましても難しいもので、
調べた時に分かりやすかったリンク9つをご紹介します。
Import-Csvコマンドについて
2.1まずCSVを読み込むにあたり必要なImport-Csvコマンドについて丁寧に解説してくれていました。
連想配列(ハッシュテーブル)について
2.2CSV内の各ドメインユーザーの情報は「A=100」のようにKey/Value形式で保管されています。Powershellではそれを連想配列と呼びます。その概念について理解しましょう!
連想配列をForeachでループする方法
2.3ユーザー1人につき1つの連想配列があるので、それをユーザーの人数分処理する必要があります。連想配列をどのように繰り返し処理するか書いてくれています。
CSVファイル内の値を連想配列として格納する方法
2.4では、上記で紹介したCSVファイルをPowershellスクリプト内で操作するためにどのように配列に格納するのか紹介してくれています。「3.2 データの配列への格納」の部分を参考にしました。
New-ADUserコマンドの解説
2.5New-ADUserコマンドの使い方やよく使うオプションに関する解説、今回やりたいことの流れがざっと分かるかなと思います。
New-ADUserコマンドのオプションとGUIにおける対応付け
2.6正直公式ナレッジを眺めたり、色んなサイトを見ていても「ところでそのオプションは何を表現しているの?」とずっと思っていました。以下サイトは有難いことに、ほぼ全てのオプションとGUI上の対応付けを一覧にしてくださっていて分かりやすいです。
New-ADUserコマンドのオプションでふりがなを指定する方法
2.7ちょっとびっくりしたのですが、New-ADUserコマンドではドメインユーザーの全ての項目をオプションで網羅しておらず、Set-ADUserコマンドで補っている部分があります。New-ADUserコマンドのオプションでもふりがなを指定できますが、マイナーなオプションを指定する必要がありSet-ADUserコマンドの方が可読性が高そうなので、今回はSet-ADUSerコマンドを使います。
ユーザプリンシパル名(UPN)とsAMAAccountNameの違い
2.8上記2つもよくつけられているオプションですがその違いが分からず調べてみました。
今回作成するスクリプトの模倣元
2.9今回は以下サイトのスクリプトを大きな柱として作成しました。
こんなに素敵なスクリプトが世に出回っていて有難い…!
3.準備~練習用CSVファイルの作成~
3.1 練習用データの生成
100人以上のランダムな名前を作るのは骨が折れるので、練習データは以下リンクから作成しました!まずデータがないと勉強できませんからね~
「生成」を押下するとCSVをダウンロードしてくれて、まぁ便利なこと!
▼選択項目
- フォーマット:CSV
- 行数:120行(120人分のデータを扱うから)
- 氏名:漢字/ひらがな/ローマ字
- 区切り:性と名を別々の項目として出力
※”区切りなし”を選択した意図は、この後Powershellで動かすにあたりスペースが名前の一部としてコマンドに認識されるのが面倒だと思ったからです。
他の選択をすることでNew-ADUserコマンドの他オプションの勉強もできそうですね!
ダウンロードしたファイル ※上記リンクから生成しているため実在する名前と関係ありません。
3.2 練習用データの編集
このままのCSVでは今回のゴールの項目ではないので、使えるようにちょっと編集します。
3.2.1 性と名を結合する(Ctrl+E)
CSVファイルの行を増やしてから、以下リンクの方法で性別と名前を結合しました。
まず列を追加して
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で紹介した以下記事の「トラブルシュート」にも値が空だと厳しそうな記載がありますね!
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 = "Stop"
5.2.2 Import-Csvコマンドとforeachコマンド
-
CSVファイル内のユーザー1人ずつforeach内のコマンドを実行する
CSVファイルをインポートしてCSVファイルの全データが格納されている$csvdata
から、foreachコマンドで1行ずつ(=ユーザー1人分のデータずつ)$userdata
に渡します。その$userdata
を基にしてforeach内のスクリプトが実行されます。
#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}"
すなわち$OU
にOU3
からOU1
の順に中身が格納されます。
さらにif($OU -ne "")
により、$OU
の中身があった場合は、$Path
の中身に従来のものに加えて$OU
の中身が足される仕組みとなっています。
そして、最後に$Path += "DC=gapteeth,DC=study"
でドメインを足します。
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.スクリプト作成中の備忘録
もちろん初心者がスラスラできるわけはなく、次回以降のために今回勉強したことを記載しておきます。基礎的でお恥ずかしいものばかりですが、ご容赦くだされ!
$null
は変数を空にするもの
6.1 プログラミング言語と同様に$null
は空を表すものかと思い、当初は$null
をif文の判定に入れていました。しかし、for文は回るがif文内のコマンドはなぜか処理されない事象に直面して$null
で判定させていることがおかしいことに気が付きました…。$null
は変数を空にするものなので、空自体を表すものではないのですね!(初心者過ぎる)
>#正しいスクリプト==================
>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 = ""
>~~~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)
>#正しいスクリプト==================
>$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 -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」さんがいらっしゃった。
7.おわりに
最後までご覧いただきありがとうございました!!新人エンジニアの勉強なので拙い部分が多くあると思うので、随時コメントでご指摘いただけると嬉しいです🙆♂️
本記事を前提として、さらにセキュリティグループを作成したり、共有フォルダをファイルサーバ上に作成しています!もし良かったらご覧ください🫡
Discussion