
TechNet コラムへようこそ。このコラムでは、よく寄せられるシステム管理スクリプトに関する質問に Scripting Guys がお答えします。システム管理スクリプトについて質問がある場合は、scripter@microsoft.com (英語のみ) までお送りください。すべての質問に回答することはできないかもしれませんが、可能な限り対応いたします。
詳細情報
| • | |
| • | |
| • |
![]()
Scripting Guy さん、よろしくお願いします。Active Directory 内のユーザー アカウントを検索し、ユーザー名を表示する HTA があります。Active Directory には 10 個の OU がありますが、そのうち 3 つの OU からのみユーザー情報を取得する必要があります。このスクリプトを記述する方法を教えてください。
-- DP

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 からユーザー アカウントを取得することはできます。
そして、さらに良い知らせは何かというと、そのとおりです。これで今日の仕事は終わりだということです。また明日お会いしましょう。