🐕

【Powershell/ActiveDirectory】階層の深さが異なるOUそれぞれにセキュリティグループをスクリプトで一括作成!

2025/02/06に公開

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

直近、ドメインユーザーをスクリプトで大量作成したので、
続いてセキュリティグループの作成にも挑戦していきますよっ!!!

1.本記事について

1.1💪勉強のゴール💪

今回のゴールは以下4点です。

1.2 前提条件

1.2.1 本記事の下準備

次に紹介する「1.2.2 前提条件詳細」と同条件で実施している以下記事の続編です。以下記事を読まなくても本記事は読み進められますが、詳細な前提を知りたい方はご覧ください。
https://zenn.dev/gapteeth/articles/04dbe0cd1cd79d

1.2.2 前提条件詳細

  1. gapteeth.studyというドメインでADサーバを作成済みです。
  2. WindowsSever2022のActiveDirectoryを利用しています。
  3. 以下画像のOUの構成を作成済みとします。
  4. ユーザ情報が格納されているCSV内ではOUの階層が上位の階層であるほど若い数字になるよう各OUの後に数字を振ります。
  5. ドメインユーザーは作成済みとします。
  6. 今回用いるCSVファイルは本記事の前編で作成されたものです。

1.3 よくある記事との違い

前記事と同様ですが、階層の深さがそれぞれ異なる全てのOUにセキュリティグループを
作成する場合のスクリプトであること
がよく出てくる記事との違いです。

1.4 読者に求める前提知識

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

2.知識収集

今回のスクリプト作成にあたり、調べた時に分かりやすかったリンク6つをご紹介します。

2.1 セキュリティグループとは

セキュリティグループとはそもそも何か、その作成方法をGUIベースで解説してくれています。今回はスクリプトで実行予定ですが、まずはGUIで行う場合はどんなものか理解する必要があると思います。

2.2 セキュリティグループの具体的な用途

セキュリティグループを作成する理由と具体的な用途を解説してくれています。

2.3 セキュリティグループのスコープ

セキュリティグループにはスコープという分類方法があります。しかし、スコープを細かく解説してくれている記事は少なく、個人的には公式が1番詳細に解説してくれていると感じました。

2.4 New-ADGroupコマンド

今回のメインであるセキュリティグループの作成コマンドの解説リンクです。

2.5 Add-ADGroupMemberコマンド

セキュリティグループにユーザーや他セキュリティグループを追加する際に使うコマンドです。

2.6 Get-ADOrganizationalUnitコマンド

OU情報(OUの名前やパス)を取得するコマンドです。今回のセキュリティグループ作成と直接の関係はありませんが、分かりやすいように各OUに所属するセキュリティグループの名前に各OU名を用いるので上記コマンドを使用しています。

3.作成したスクリプトと大まかな流れ

3.1 スクリプトの大まかな流れ

セキュリティグループ作成の大まかな流れは以下の通りです。

  1. 全OUの名前とパスの情報を配列に格納する
  2. 1の情報を用いて各OUに対するセキュリティグループを作成
  3. 「あるOU」…①に対して「入れ子の階層のOU」…②にあるセキュルティグループ名を取得。
    全ての②のセキュリティグループに対して①のセキュルティグループを所属させる。
  4. ドメインユーザーを各OUにあるセキュリティグループに所属させる。

3.2 スクリプト

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

セキュリティグループ作成スクリプト
<#1:全OUの名前とパスの情報を配列に格納する-----------------------------------------------------------------------------------------------------#>
#OU名とそのパスを格納する連想配列を定義
$All_OU = @{}
#全OU名を取得。(=全セキュリティグループ名とそのパスを取得。/セキュリティグループ名はOU名から派生したものをつける。)
$All_OU = Get-ADOrganizationalUnit -Filter "*" -SearchBase "DC=gapteeth,DC=study" -SearchScope Subtree |
Select-Object Name,DistinguishedName


<#2:各OUに対するセキュリティグループを作成-----------------------------------------------------------------------------------------------------#>
#例)OU名「人事課」にセキュリティグループ名「人事課-SG」がある。
foreach($Make_SG in $All_OU){

    #セキュリティグループ名は「OU名-SG」と定義する。
    $SG_Name = "$($Make_SG.Name)-SG"#連想配列の時は通常の変数を$()で囲む。

    #セキュリティグループを作成する。
    New-ADGroup -Name $SG_Name -GroupScope DomainLocal -Path $Make_SG.DistinguishedName`
    -GroupCategory Security -Description $SG_Name
}

<#3:入れ子の階層のOUにあるセキュルティグループへ上位階層のセキュルティグループを所属させる。---------------------------------------------------------#>
foreach($Target_OU in $All_OU){
    #対象の階層にあるセキュリティグループ名を定義
    $Target_SG = "$($Target_OU.Name)-SG"

    #入れ子の階層にある全てのOU名を再帰的に抽出(=追加の必要がある全てのセキュリティグループの名前抽出)
    $Nesting_OU = Get-ADOrganizationalUnit -Filter "*" -SearchBase $Target_OU.DistinguishedName -SearchScope Subtree |
    Select-Object Name |
    Where-Object { $_.Name -ne $Target_OU.Name } #対象のセキュリティグループを除く。
  
    #入れ子になっているセキュルティグループに対して上位階層のセキュリティグループを所属させる。
    foreach($Add_OU in $Nesting_OU){
        #加えるセキュリティグループがあるとき(=$Add_OUが空ではないとき=入れ子の階層が入れ子になっているセキュリティグループが存在する時)
        if($Add_OU -ne ""){
            #入れ子となっているセキュリティグループ名を定義
            $Add_SG = "$($Add_OU.Name)-SG"
            #セキュリティグループの追加
            Add-ADGroupMember $Add_SG $Target_SG
        }
        else{
            break;
        }
    }
}

<#4:ドメインユーザーを各OUにあるセキュリティグループに追加する。--------------------------------------------------------------------------------#>
#ドメインユーザを作成したCSVを用いる。
$csvdata = Import-Csv -Path "C:\Users\Administrator\Powershellスクリプト\New-ADUser練習用.csv"
#各ユーザが所属する1番下のOUを調べる
foreach ($userdata in $csvdata){
    #OU3(最も下位の階層)から中身があるか確認する。
    #OUが最初に見つかった時の階層がそのユーザーが所属するべきセキュリティグループのある階層。
    for($i = 3;$i -ge 1;$i--){
        #ユーザーのOUの情報を$OUに格納する。
        $OU = $userdata."OU${i}"
        #OU名が存在するとき(=そのユーザーが所属するべきセキュリティグループのある階層のOU)
        if($OU -ne ""){
            #該当のセキュリティグループ名を定義する
            $Belong_SG = "${OU}-SG"
            #各ユーザをセキュリティグループに追加する。
            Add-ADGroupMember $Belong_SG $userdata.Name  
            #さらに上の階層のセキュリティグループに所属しないようにFor文をBreakする。
            Break;
        }
    }
}

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

4.スクリプト作成の意図

4.1 全OUの名前とパスの情報を配列に格納する

セキュリティグループ作成スクリプト
<#全OUの名前とパスの情報を配列に格納する-----------------------------------------------------------------------------------------------------#>
#OU名とそのパスを格納する連想配列を定義
$All_OU = @{}
#全OU名を取得。(=全セキュリティグループ名とそのパスを取得。/セキュリティグループ名はOU名から派生したものをつける。)
$All_OU = Get-ADOrganizationalUnit -Filter "*" -SearchBase "DC=gapteeth,DC=study" -SearchScope Subtree |
Select-Object Name,DistinguishedName

4.1.1 $All_OU = @{}

  • OU情報を格納する連想配列を格納する
    $All_OUの中身はSelect-Object Name,DistinguishedNameで抽出したことにより、OU毎に"Name"=…,"DistinguishedName"=…の形で格納された連想配列です。したがって、上記のように定義しました。

4.1.2 Get-ADOrganizationalUnit -Filter "*" -SearchBase "DC=gapteeth,DC=study" -SearchScope Subtree

  • ドメイン内既存のOU情報を全て取得する
    2.6 Get-ADOrganizationalUnitコマンドでオプションの詳細は紹介されています。
    -SearchBase "DC=gapteeth,DC=study":特定のOU配下ではなくドメイン全体のOUを指定しています。
    -SearchScope Subtree:再帰的に全OUの情報を取得できるようにしています。
    ▼ドメイン内OUの構造

4.1.3 Select-Object Name,DistinguishedName

  • NameとDistinguishedName(=OUのパス)のみを指定して取得
    それぞれセキュリティグループ作成時に以下のように利用します。
    ・OU名(Name):セキュリティグループ名で利用。
    ・OUのパス(DistinguishedName):セキュリティグループを各OUに所属させるために利用。

4.2 各OUに対するセキュリティグループを作成

セキュリティグループ作成スクリプト
<#2:各OUに対するセキュリティグループを作成-----------------------------------------------------------------------------------------------------#>
#例)OU名「人事課」にセキュリティグループ名「人事課-SG」がある。
foreach($Make_SG in $All_OU){

    #セキュリティグループ名は「OU名-SG」と定義する。
    $SG_Name = "$($Make_SG.Name)-SG"

    #セキュリティグループを作成する。
    New-ADGroup -Name $SG_Name -GroupScope DomainLocal -Path $Make_SG.DistinguishedName`
    -GroupCategory Security -Description $SG_Name
}

4.2.1 New-ADGroup -Name $SG_Name -GroupScope DomainLocal -Path $Make_SG.DistinguishedName -GroupCategory Security -Description $SG_Name

  • セキュリティグループを作成
    それぞれのオプションでは以下を意図しています。オプションの詳細は、2.4 New-ADGroupコマンドで記載されています。
    -Name $SG_Name:セキュリティグループ名は「OU名-SG」とする。
    -Path $Make_SG.DistinguishedName:OUのパスを指定してセキュリティグループをOUに関連付ける。
    -GroupCategory Security:作成グループをセキュリティグループと指定。

4.3 「foreachにおける対象のOU(所属元)」…①に対して「入れ子の階層のOU(所属先)」…②にあるセキュルティグループ名を取得。全ての②のセキュリティグループに対して①のセキュルティグループを所属させる。

セキュリティグループ作成スクリプト
<#3:入れ子の階層のOUにあるセキュルティグループへ上位階層のセキュルティグループを所属させる。---------------------------------------------------------#>
foreach($Target_OU in $All_OU){
    #対象の階層にあるセキュリティグループ名を定義
    $Target_SG = "$($Target_OU.Name)-SG"

    #入れ子の階層にある全てのOU名を再帰的に抽出(=追加の必要がある全てのセキュリティグループの名前抽出)
    $Nesting_OU = Get-ADOrganizationalUnit -Filter "*" -SearchBase $Target_OU.DistinguishedName -SearchScope Subtree |
    Select-Object Name |
    Where-Object { $_.Name -ne $Target_OU.Name } #対象のセキュリティグループを除く。
  
    #入れ子になっているセキュルティグループに対して上位階層のセキュリティグループを所属させる。
    foreach($Add_OU in $Nesting_OU){
        #加えるセキュリティグループがあるとき(=$Add_OUが空ではないとき=入れ子の階層が入れ子になっているセキュリティグループが存在する時)
        if($Add_OU -ne ""){
            #入れ子となっているセキュリティグループ名を定義
            $Add_SG = "$($Add_OU.Name)-SG"
            #セキュリティグループの追加
            Add-ADGroupMember $Add_SG $Target_SG
        }
        else{
            break;
        }
    }
}

4.3.1 $Nesting_OU = Get-ADOrganizationalUnit -Filter "*" -SearchBase $Target_OU.DistinguishedName -SearchScope Subtree | Select-Object Name

  • foreach対象OU(所属元)配下にある全ての入れ子のOU(所属先)情報を取得する
    詳細は4.1.2とほぼ同様ですが、-SearchBase $Target_OU.DistinguishedNameにより、foreachコマンドで流れてきた対象OU名(所属元)とそのOUに対して入れ子のOU名(所属先)のみに限定されています。
    ▼例==================================================
    【例1】$Target_OU=総務部の時に上記コマンドで取得できる対象OU名は以下5つ
    総務部/人事課/経理課/採用Gr/労務Gr
    【例2】$Target_OU=人事課の時に上記コマンドで取得できる対象OU名は以下3つ
    人事課/採用Gr/労務Gr

    総務部のOU構造
    ====================================================

4.3.2 Where-Object { $_.Name -ne $Target_OU.Name }

  • 対象OU(所属元)を取得情報から省く
    4.3.1にて-SearchScope Subtreeを指定すると、foreach対象のOU(=-SearchBase $Target_OU.DistinguishedNameで指定したOU)を含む全てのOUを再帰的に取得します。今回はforeach対象OU名(所属元/$Target_OU.Name)は不要のため、Where-Object { $_.Name -ne $Target_OU.Name } で対象OUを除きました。したがって、$Nesting_OUに含まれるのはforeach対象OUを除いた入れ子のOU名となります。
    ▼例==================================================
    【例1】$Target_OU=総務部の時に上記コマンドで取得できる対象OU名は以下4つ
    人事課/経理課/採用Gr/労務Gr ⇒総務部がない!
    【例2】$Target_OU=人事課の時に上記コマンドで取得できる対象OU名は以下2つ
    採用Gr/労務Gr ⇒人事課がない!

    総務部のOU構造
    ====================================================

4.3.3 if($Add_OU -ne "")

  • 再帰的にOUを探した結果、情報があるかないかを判断する。
    4.3.1にて再帰的に探した結果がないときは、foreach対象OU($Target_OU)に対して入れ子のOU($Add_OU=$Nesting_OU)がなく、所属させるセキュリティグループがないときです。したがって、foreach対象のセキュリティグループを入れ子のセキュリティグループに所属させられないので、セキュリティグループ所属コマンドの直前に再帰的に探した結果の有無を判定させています。

4.3.3 Add-ADGroupMember $Add_SG $Target_SG

  • 入れ子となっている全てのセキュリティグループに対してforeach対象のセキュリティグループを所属させる。
    入れ子となっている全てのセキュリティグループ(Add_SG)に対してforeach対象のセキュリティグループ(Target_SG)を所属させると、入れ子となっている全てのセキュリティグループ(Add_SG)で可能なことは、上位のセキュリティグループ(Target_SG)でも可能となります。※ただし、その逆は不可。

4.4 ドメインユーザーを各OUにあるセキュリティグループに所属させる。

セキュリティグループ作成スクリプト
<#ドメインユーザーを各OUにあるセキュリティグループに追加する。--------------------------------------------------------------------------------#>
#ドメインユーザを作成したCSVを用いる。
$csvdata = Import-Csv -Path "C:\Users\Administrator\Powershellスクリプト\New-ADUser練習用.csv"
#各ユーザが所属する最下層のOUを調べる
foreach ($userdata in $csvdata){
    #OU3(最も下位の階層)から順に中身があるか確認する。
    #OUが最初に見つかった時の階層がそのユーザーが所属するべきセキュリティグループのある階層。
    for($i = 3;$i -ge 1;$i--){
        #ユーザーのOUの情報を$OUに格納する。
        $OU = $userdata."OU${i}"
        #OU名が存在するとき(=そのユーザーが所属するべきセキュリティグループのある階層のOU)
        if($OU -ne ""){
            #該当のセキュリティグループ名を定義する
            $Belong_SG = "${OU}-SG"
            #各ユーザをセキュリティグループに追加する。
            Add-ADGroupMember $Belong_SG $userdata.Name  
            #さらに上の階層のセキュリティグループに所属しないようにFor文をBreakする。
            Break;
        }
    }
}

4.4.1 $csvdata = Import-Csv -Path "C:\Users\Administrator\Powershellスクリプト\New-ADUser練習用.csv"

  • OUの階層が上位の階層であるほど若い数字になるよう各OUの後に数字を振る。
    「1.2.2 前提条件詳細」にも書きましたが、上記が反映さえたCSVファイル(画像)を使用しています。

    実際のCSVファイルの中身

4.4.2 for($i = 3;$i -ge 1;$i--){$OU = $userdata."OU${i}"~中略~}

  • foreach対象ユーザがどの階層にいるか下位の階層(OU3)から探す。
    for文は「iは3からスタートしてiが1以上ならば1つずつ小さくしつづける」という条件文でを回しています。iの中身が3から1ずつ小さくなっていくので、$userdata."OU${i}"すなわち$OUにOU3からOU1の順に中身が格納されます。

4.4.3 if($OU -ne ""){$Belong_SG = "${OU}-SG"~中略~}

  • foreach対象ユーザがどの階層にいるか下位の階層(OU3)から探す。
    if($OU -ne "")により、$OUの有無を判定します。OUの中身があった場合(=if文がTrueのとき)は、そのOUがforeach対象ユーザの所属するOUです。
    また、$Belong_SG = "${OU}-SG"を使い、その階層のOU名("${OU}-SG")からセキュリティグループ名($Belong_SG)を導出します。

4.4.4 Add-ADGroupMember $Belong_SG $userdata.Name

  • foreach対象ユーザをそのユーザーが所属するOUのセキュリティグループに所属させる
    上記の通り、4.4の1番大きな目的を達成しました。

4.4.5 Break;

  • foreach対象ユーザが誤ったセキュリティグループに所属しないようにする
    for文を全て回すと、$userdata."OU${i}"にOU3~OU1が順に入り、foreach対象ユーザの所属するOUの最上位層にあるOUのセキュリティグループにまでforeach対象ユーザを所属させてしまいます。(例:採用Grに所属する人が「採用Gr-SG」だけでなく、「人事課-SG」と「総務部-SG」にまで所属してしまう)
    上記を防ぐために、最初にif文に入った時(=foreach対象ユーザが所属するOUが見つかった時)にfor文から抜け出せるようBreak;を記載しています。

    総務部のOU構造

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

5.1 OUと同じ名前のセキュリティグループは作れない

下記エラーコードのNew-ADGroup -Name "Domain Controllers"から分かるように、当初「OU名=セキュリティグループ名」となるようにコマンドを指定したところ、**指定されたグループは既に存在します。とエラーが出てしまいました。**それゆえに、今回はセキュリティグループ名を「OU名-SG」と設定しています。

エラーコード
S C:\Users\Administrator> New-ADGroup -Name "Domain Controllers" -GroupScope DomainLocal -Path "OU=Domain Controllers,DC=gapteeth,DC=study" -GroupCategory Security  -Description $SG_Name
New-ADGroup : 指定されたグループは既に存在します。
発生場所 行:1 文字:1
+ New-ADGroup -Name "Domain Controllers" -GroupScope DomainLocal -Path  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (CN=Domain Contr...pteeth,DC=study:String) [New-ADGroup], ADException
    + FullyQualifiedErrorId : ActiveDirectoryServer:1318,Microsoft.ActiveDirectory.Management.Commands.NewADGroup

5.2 「|」の後ろでもSelect-Objectコマンドは「$_.」が不要

途中でセキュリティグループ名の頭に$_.が付いていることに気が付き、どの段階の変数で$_.を入れてしまっているのか調査したところ、まさかの冒頭でした…。Select-Objectの指定に「$_.」を付けた時の連想配列の中身を表示させると以下の通りでした。

$All_SG = Get-ADOrganizationalUnit -Filter "*" -SearchBase "DC=gapteeth,DC=study"`
-SearchScope Subtree | Select-Object {$_.Name},{$_.DistinguishedName}

Write-Host $All_SG

@{$_.Name=Domain Controllers; $_.DistinguishedName=OU=Domain Controllers,DC=gapteeth,DC=study}
@{$_.Name=営業部; $_.DistinguishedName=OU=営業部,DC=gapteeth,DC=study}
@{$_.Name=第1課; $_.DistinguishedName=OU=第1課,OU=営業部,DC=gapteeth,DC=study}
@{$_.Name=社長; $_.DistinguishedName=OU=社長,DC=gapteeth,DC=study}
…(以下同様なので省略)

6.おわりに

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

次は、ファイルサーバを準備して共有フォルダを実際に作成してセキュリティグループを所属させていきます!記事の作成まで終わったら、この場所にリンクを貼り付けますね🙂
【2025/2/13追記】
お待たせしました!上記完了して記事を作成したのでリンクを貼り付けますっ!!!
https://zenn.dev/gapteeth/articles/6872d458c95d79

Discussion