Hey, Scripting Guy!

Scripting Guys が皆さんの質問にお答えします

Hey, Scripting Guy!

TechNet コラムへようこそ。このコラムでは、よく寄せられるシステム管理スクリプトに関する質問に Scripting Guys がお答えします。システム管理スクリプトについて質問がある場合は、scripter@microsoft.com (英語のみ) までお送りください。すべての質問に回答することはできないかもしれませんが、可能な限り対応いたします。

詳細情報

Hey, Scripting Guy! カテゴリ別アーカイブ

Hey, Scripting Guy! 日付別アーカイブ

Hey, Scripting Guy! ダウンロード

Spacer

*

Active Directory 内の複数の OU を検索する方法はありますか

Hey, Scripting Guy! Question

Scripting Guy さん、よろしくお願いします。Active Directory 内のユーザー アカウントを検索し、ユーザー名を表示する HTA があります。Active Directory には 10 個の OU がありますが、そのうち 3 つの OU からのみユーザー情報を取得する必要があります。このスクリプトを記述する方法を教えてください。

-- DP

SpacerHey, Scripting Guy! AnswerScript Center

DP さん、こんにちは。ここで、ちょっとした内緒話をお教えしましょう。書き手が手抜きをしていて、まじめにやろうという気さえないとき、それはとても簡単にわかります。どうやってわかるのか、ですって。それは、そのような場合に彼らは必ず、「良い知らせと悪い知らせがあります」などの陳腐なお決まりのフレーズを使用して記事を書き始めるためです。ちょっとした注意点にすぎませんが、怠惰でやる気のない物書きについて、Scripting Guys がどう感じているかはご存知ですよね。それでは、Active Directory の検索に関する質問についてですが、実は良い知らせと悪い知らせがあります。まず、悪い知らせをお教えしましょう。DP さんは、Active Directory 内のユーザー アカウントを検索し、10 個ある OU のうち 3 つの OU のみからアカウントを取得する必要があるのですね。実は、なんと聞いてください。それはできません (少なくとも、簡単にはできません)。その理由は、なぜか Active Directory には、ユーザー アカウント (またはその他のオブジェクト) が存在する OU を特定するプロパティがないためです。理論上は、ワイルドカードを使用した複雑なクエリを記述することで、うまくいく "可能性" もあります。しかし、次の架空の Where 句のように、簡単でわかりやすい方法を使うことはできません。

Where OU='Finance' OR OU='Research' OR OU="Shipping'

どうしてもできないのです。

とにかく DP さん、悪い知らせで申し訳ありません。ではまた明日。

そうでした。編集者からの "ありがたい" 指摘どおり、お決まりの「良い知らせと悪い知らせ」というフレーズを使うときには、必ず良い知らせも伝えるのが決まりです (残念ながら、良い知らせを伝えるのは、悪い知らせを伝えるよりも大変な作業なので、良い知らせを後回しにしたのは、そのためです。それに、「知らせがないのは良い知らせ」とはよく言ったものですよね)。ですが、ご存知のとおり、編集者の言うことは絶対です。したがって、DP さんに良い知らせをお伝えすると、指定した複数の OU からユーザー情報を取得することはできます。それには、複数回検索を実行するだけです (Active Directory 検索 (特に、単一の OU を対象とした検索) は非常に高速ですから、心配無用です)。そして、さらに良い知らせがあります。ここで紹介する HTA では、リスト ボックスに "すべて" の OU が表示され、ユーザーは、この一覧から必要な数の OU を選択することが可能で、選択された各 OU のユーザー アカウントに関する情報を取得できます。

確かに、これは、私たちがこの 1 週間に耳にした中で最も良い知らせでもあります。

しかし、私たちの職場はマイクロソフトであるため、本当に良い知らせを耳にする機会はあまりありません (Scripting Guys からのアドバイス : 編集者の前では決して MTPS という単語を口にしてはいけません。まじめな話です)。

ともあれ、コードは次のとおりです。

<SCRIPT Language="VBScript"> 
 
    Const ADS_SCOPE_SUBTREE = 2 
    Const ADS_SCOPE_ONELEVEL = 1 
 
    Set objConnection = CreateObject("ADODB.Connection") 
    Set objCommand =   CreateObject("ADODB.Command") 
    objConnection.Provider = "ADsDSOObject" 
    objConnection.Open "Active Directory Provider" 
    Set objCommand.ActiveConnection = objConnection 
 
    objCommand.Properties("Page Size") = 1000 
 
    Sub Window_onLoad 
        objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE  
        objCommand.CommandText = _ 
            "SELECT Name, ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE " & _ 
                "objectCategory='organizationalUnit' ORDER By Name"   
        Set objRecordSet = objCommand.Execute 
 
        objRecordSet.MoveFirst 
 
        strHTML = "<select size = '20' name='OUList' style='width:300px'>" 
 
        Do Until objRecordSet.EOF   
            Set objOption = Document.createElement("OPTION") 
            objOption.Text = objRecordSet.Fields("Name").Value 
            objOption.Value = objRecordSet.Fields("ADsPath").Value 
            MyOUs.Add(objOption) 
            objRecordSet.MoveNext 
         Loop 
 
    End Sub 
 
    Sub SearchForUsers 
        For i = 0 to (MyOUs.Options.Length - 1) 
            If (MyOUs.Options(i).Selected) Then 
                strSearchOU = MyOUs.Options(i).Value  
                objCommand.Properties("Searchscope") = ADS_SCOPE_ONELEVEL  
 
                objCommand.CommandText = _ 
                    "SELECT Name FROM '" & strSearchOU & "' WHERE objectCategory='user'"   
                Set objRecordSet = objCommand.Execute 
 
                If objRecordSet.RecordCount > 0 Then 
                    objRecordSet.MoveFirst 
                    Do Until objRecordSet.EOF 
                        strNames = strNames & objRecordSet.Fields("Name").Value & "<BR>" 
                        objRecordSet.MoveNext 
                    Loop 
                End If 
            End If 
        Next 
        UserList.InnerHTML = strNames 
    End Sub 
 
</SCRIPT> 
 
<body> 
    <select size="10" name="MyOUs" style="width:400" multiple></select><p> 
    <input type="button" value="Get Users" onClick="SearchForUsers"><p> 
    <div id="UserList"></div> 
</body>

そうです。確かに、これは長大なコード ブロックですね。でも心配しないでください。このコードのしくみは、すべて説明します。

: ただ、すべてのしくみを説明しない部分は除きます。たとえば、Active Directory 検索の基本原理の詳細については説明しません。その点については、Tales From the Script コラムのシリーズ「プリンタはどこ? : スクリプトを使って Active Directory を検索する」(全 2 部) を参照することをお勧めします。また、OU を表示する動的なリスト ボックスを作成するプロセスについても、詳しい説明はしません。というのも、既に以前のコラムで、動的なリスト ボックスを作成する手順を説明していますので、この説明が必要な場合は、こちらのコラムを参照してください。そして・・・。もうおわかりですね。

まず、この HTA で使用している HTML オブジェクトを見てみましょう。

<select size="10" name="MyOUs" style="width:400" multiple></select><p> 
<input type="button" value="Get Users" onClick="SearchForUsers"><p> 
<div id="UserList"></div>

ご覧のとおり、この HTA は次の 3 つのオブジェクトで構成されています。

MyOU という名前の複数選択リストボックス : どうしてこれが複数選択リスト ボックスだとわかるのか、ですって。それは、multiple パラメータが追加されているためです。複数選択リスト ボックスとは何かというと、単純に複数の項目を選択できるリスト ボックスのことです。ユーザは必要な数の項目を選択でき、選択された各項目に対してサブルーチンが実行されます (このサブルーチンについては、後で説明します)。

Get Users というラベルの付いたボタン : このボタンがクリックされると、SearchForUsers という名前のサブルーチンがトリガされます。名前からわかるように、これは選択された OU のユーザーを検索するサブルーチンです。ここでの基本前提は、ユーザーがリスト ボックスで必要な数の OU を選択し、[Get Users] ボタンをクリックすることです。その結果、SearchForUsers サブルーチンによって、選択された OU からユーザー アカウントの情報が取得されます。それから・・・。はい、いいところにお気付きです。サブルーチンでは、その情報を使用して何をするのでしょうか (ヒント : 答えは次の箇条書きの中にあります)。

UserList という ID が指定された <DIV>: <DIV> は、単に HTA の名前の付いた領域ですが、この領域の内容は、プログラムで操作できます。ご覧のとおり、この時点では <DIV> には内容が含まれていません。ただし、これは一時的な状態にすぎません。最終的には、SearchForUsers サブルーチンによって、取得されたユーザー名が HTA のこの部分に記述されます。

おわかりいただけましたか。それは良かったです。では、スクリプトの説明に入りましょう。

: 良い質問ですね。スクリプトの説明は、良い知らせまたは悪い知らせのどちらでしょうか。その答えはすぐに判明することでしょう。

お気付きかもしれませんが、この HTA の冒頭には <SCRIPT> タグがあり、そのすぐ後に、サブルーチンに含まれない次のコード行が追加されています。

Const ADS_SCOPE_SUBTREE = 2 
Const ADS_SCOPE_ONELEVEL = 1 
 
Set objConnection = CreateObject("ADODB.Connection") 
Set objCommand =   CreateObject("ADODB.Command") 
objConnection.Provider = "ADsDSOObject" 
objConnection.Open "Active Directory Provider" 
Set objCommand.ActiveConnection = objConnection 
 
objCommand.Properties("Page Size") = 1000

なぜ、これらのコマンドをサブルーチンに含めなかったのでしょうか。ここでは、2 つの定数 (ADS_SCOPE_SUBTREE と ADS_SCOPE_ONELEVEL) を定義し、2 つのオブジェクト (ADODB.Connection と ADODB.Command) を作成してから、これらのオブジェクトの一部のプロパティを構成しています。ここでは、これらの定数とオブジェクトをグローバル定数とグローバル オブジェクトにする、つまり、HTA のすべてのサブルーチンで使用できるようにする必要があります。その最も簡単な方法は、コードをサブルーチン内ではなく、<SCRIPT> タグ内に配置することです。ここでは、まさにその処理を行っています。

: ちなみに、これらのコマンドの詳細については、Tales From the Script コラムのシリーズを参照してください。

次に、Window_OnLoad および SearchForUsers という 2 つのサブルーチンについて説明します。まず注意しておきたいのは、Window_OnLoad という名前を選んだのは、単にその響きが好きだからというだけではありません (どちらかと言えば確かに語呂が良いですが)。この名前を選択したのは、Window_OnLoad という名前のサブルーチンは HTA が開かれたり更新されるたびに自動的に実行される、という理由からです。OU 名の一覧を取得するコードを、Window_OnLoad という名前のサブルーチンに含めることで、HTA を起動するたびに、Active Directory 内のすべての OU がリスト ボックスに確実に表示されるようにします。

今日のコラムでは、OU 名を取得する方法についても、詳しくは説明しません。代わりに、ここでは単に使用した次の SQL クエリに重点を置いて説明します。

objCommand.CommandText = _ 
    "SELECT Name, ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE " & _ 
        "objectCategory='organizationalUnit' ORDER By Name"

ご覧のとおり、objectCategory プロパティの値が organizationalUnit であるドメイン (fabrikam.com) 内のすべてのオブジェクトについて、2 つの属性 (Name と ADsPath) の値を返すようにスクリプトに要求しています (また、それらのオブジェクトを Name プロパティの値に基づいてアルファベット順に並べ替えています)。どうして、ドメイン内の "すべて" の OU が返されることがわかるのか、ですって。それは、クエリを実行する直前に、Searchscope プロパティの値を ADS_SCOPE_SUBTREE に設定しているためです。

objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE

このように設定すると、スクリプトでは、指定されたコンテナ (fabrikam.com) だけでなく、すべてのサブコンテナ (たとえば、すべての OU とサブ OU) が検索されます。Active Directory のルートで検索を開始したため、スクリプトによって、ディレクトリ サービス全体が入念に検索され、Active Directory に含まれる各 OU が返されます。

すべての OU 名を含むレコードセットが返されたら、次のコード ブロックを使用して、各 OU をリスト ボックスに追加します。

Do Until objRecordSet.EOF   
    Set objOption = Document.createElement("OPTION") 
    objOption.Text = objRecordSet.Fields("Name").Value 
    objOption.Value = objRecordSet.Fields("ADsPath").Value 
    MyOUs.Add(objOption) 
    objRecordSet.MoveNext 
Loop

詳しい説明は抜きにして、レコードセットのレコード (OU) ごとに Option オブジェクトのインスタンスを作成します。OU の Name プロパティの値を Text プロパティ (リスト ボックスに表示されるラベル) に、ADsPath プロパティの値を Value プロパティ (SearchForUsers サブルーチンで使用される実際の情報) にそれぞれ代入します。それぞれの値を代入したら、Add メソッドを呼び出して、この新しいオプションをリスト ボックスに追加します。

HTA を実行するだけで大変な作業ではないか、ですって。確かにそのとおりです。悪い知らせは、ここで処理を行うために、ちょっとした準備作業が必要であるということです。ただし、悪い知らせがあるとなれば答えは 1 つ、良い知らせもあるに違いありません。良い知らせは、これでユーザー アカウントの検索を始める準備ができたということです。

その前に、もちろん、検索する OU を特定する必要があります。そのための最も簡単な方法は、次のコード ブロックを実行することです。

For i = 0 to (MyOUs.Options.Length - 1) 
    If (MyOUs.Options(i).Selected) Then 
        strSearchOU = MyOUs.Options(i).Value

ここでは、0 〜 n (リスト ボックスの Length プロパティの値から 1 を引いた数) 回実行される For Next ループを設定します。なぜ 0 かというと、VBScript のコレクションでは、最初の項目のインデックス番号が必ず 0 であるためです。ではなぜ、項目の数から "1 を引く" のでしょうか。たとえば、リスト ボックスで 3 つの項目が選択されているとします。最初の項目のインデックス番号は 0、2 番目の項目のインデックス番号は 1、3 番目の項目のインデックス番号は 2 となります。コレクション内の最後の項目のインデックス番号は、必ずその配列内の項目の総数より 1 小さい数です。

奇妙ですが、これでうまくいくのです。

リスト ボックス内の項目ごとに、Selected プロパティが True かどうかを確認します。True の場合には、その OU が選択されていて、検索する必要があることを意味します。その検索を実行するため、(OU の ADsPath プロパティに相当する) Value プロパティの値を取得し、strSearchOU という名前の変数に格納します。

続いて、次に、他の 2 つの処理を実行します。まず、Searchscope プロパティを ADS_SCOPE_ONELEVEL に設定する必要があります。

objCommand.Properties("Searchscope") = ADS_SCOPE_ONELEVEL

なぜでしょうか。それは、この定数で、指定された OU "のみ" を検索するようにスクリプトに指示するためです。子 OU は検索されません。こうして、検索対象を単一の OU に制限します。

次に、SQL クエリを定義する必要があります。

objCommand.CommandText = _ 
    "SELECT Name FROM '" & strSearchOU & "' WHERE objectCategory='user'"

このクエリでは、単純に、選択された OU を検索し、objectCategroy プロパティの値が user であるすべてのオブジェクトの Name プロパティ値を取得します。言うまでもなく、この処理によって OU に格納されている各ユーザーに関する情報を含むレコードセットが返されます。その後、そのレコードセット内のユーザーごとに、ユーザー名と (HTML では復帰改行文字に相当する) <BR> タグを追加します。

strNames = strNames & objRecordSet.Fields("Name").Value & "<BR>"

リスト ボックスで複数の OU を選択した場合にはどうなるのでしょうか。その場合も問題ありません。最初の OU の処理が完了したら、ループ処理の先頭に戻り、選択された項目の検索を続けます。選択された項目を見つけたら、その OU を使用して同じ処理を繰り返します。

リスト ボックスのすべての項目について処理が完了したら、次のコード行を使用して、ユーザー名を <DIV> に記述します。

UserList.InnerHTML = strNames

DP さん、これでうまくいくでしょう。悪い知らせは、これがあまり凝った HTA ではない点です。返された情報をわかりやすい状態で表示するには、明らかに追加の処理が必要です (ちなみに、ここで優れた方法を 1 つご紹介しています)。良い知らせはというと、確かにこの方法では少し回り道をしていて、3 回の検索を個別に行う必要がありますが、少なくとも 3 つの異なる OU からユーザー アカウントを取得することはできます。

そして、さらに良い知らせは何かというと、そのとおりです。これで今日の仕事は終わりだということです。また明日お会いしましょう。


ページのトップへページのトップへ