前々回に少し書きましたが ユーザやグループなどの情報は System.DirectoryServices 名前空間にある DirectoryEntry クラスのオブジェクトで表されます。
これを取得するには検索する必要があるんですが、この時にクエリを実行するのが DirectorySearcher クラスです。
クエリを実行すると SearchResult クラスまたは SearchResultCollection クラスのインスタンスが返されます。
DirectoryEntry と DirectorySearcher は Visual Studio のツールボックスのコンポーネントのところにありますね。(使ったことないけどw)
検索する Active Directory 内のデータは主に次の 6つだと思います。
・ユーザ
・グループ
・コンピュータ
・組織単位(OU)
・プリンタ
・共有フォルダ
これらは管理者が管理ツール「Active Directory ユーザとコンピュータ」で主に管理するオブジェクトですね。(クリックすると新しいウィンドウで拡大図が表示されます。)
ユーザとグループについてはローカル(クライアントPCやメンバサーバなど)も検索できます。
流れとしては次のようになります。(ローカルの場合は異なります。1.だけ同じ)
1. 検索のルートとなる DirectoryEntry を作成
2. 検索フィルタ(SQLでいう抽出条件)を指定
3. 検索のクエリを実行
4. 検索結果の DirectoryEntry を取得
まずはサンプル。
ユーザのリストを取得
Public Shared Function GetUsers() As IList(Of DirectoryEntry)
Dim users As New Collection(Of DirectoryEntry)()
Using root As New DirectoryEntry(LdapRootPath)
If IsLogonDomain Then 'ドメインにログオンしている時
Dim filter = "(objectCategory=User)"
Using searcher As New DirectorySearcher(root, filter)
Using results = searcher.FindAll()
For Each res As SearchResult In results
users.Add(res.GetDirectoryEntry())
Next
End Using
End Using
Else 'ドメインにログオンしていない時
For Each entry As DirectoryEntry In root.Children
If entry.SchemaClassName.Equals(“User”) Then
users.Add(entry)
End If
Next
End If
End Using
Return users
End Function
指定した名前のグループを検索
Public Shared Function FindGroup(name As String) As DirectoryEntry
If name Is Nothing Then
Throw New ArgumentNullException("name")
End If
Using root As New DirectoryEntry(LdapRootPath)
If IsLogonDomain Then 'ドメインにログオンしている時
Dim filter = String.Format("(&(objectCategory=Group)(name={0}))", name)
Using searcher As New DirectorySearcher(root, filter)
Dim result = searcher.FindOne()
Return If(result Is Nothing, Nothing, result.GetDirectoryEntry())
End Using
Else 'ドメインにログオンしていない時
Return root.Children.Find(name, "Group")
End If
End Using
End Function
検索のルートとなるパス(前々回に書いた LdapRootPath プロパティ)を指定するんですが、ログオン先によってプロバイダが異なるので、ドメインにログオンしてるかどうか(IsLogonDomain プロパティ)で判別します。
・ドメインにログオンしている場合
プロバイダ:LDAP(Lightweight Directory Access Protocol)
書式例:LDAP://DC=virtual,DC=proceed,DC=local
※この例の場合、ドメインは virtual.proceed.local です。
・ローカルログオンしている場合
プロバイダ:WinNT(Windows NT)
書式例:WinNT://vpc-testclient1
※この例の場合、コンピュータ名は vpc-testclient1 です。
検索フィルタ(LDAP書式のフィルター文字列)は “(プロパティ名=値)” のように記述します。
例:
"(objectCategory=User)“ ‘ユーザ
"(&(objectCategory=Group)(name=De*))” ‘De で始まるグループ
"(&(objectCategory=PrintQueue)(|(printColor=True)(printPagesPerMinute>=30)))" ‘カラーか 1分間に30枚以上印刷できるプリンタ
ユーザを検索する場合は "(objectCategory=Person)” としてもいけるはず。(むしろこっちがいいのかも)
オブジェクトの種類を指定するには "(objectCategory=○○)“ としてます。
管理ツール「Active Directory ユーザとコンピュータ」で何かのオブジェクトのプロパティを表示して「オブジェクト」タブを見ると、「オブジェクト クラス」という項目があります。そこにオブジェクトの種類が表示されているので "(objectClass=○○)“ じゃないの?と思うんですが(それは過去のことなのかな?)、これだと想定外の結果になることがあります。
まずはオブジェクトごとにどんなプロパティがあって、その値は何なのかを確認してみます。
For Each pname As String In entry.Properties.PropertyNames ‘entry は DirectoryEntry オブジェクト
Dim val = String.Format("Name={0}, Value={1}", pname, entry.Properties.Item(pname).Value)
Next
Item プロパティは PropertyValueCollection クラスのインスタンスなので値が 1つとは限らないんです。そしてその Value プロパティは Object 型です。が、この Value プロパティの値ってコレクションの要素の数によって異なります。
・要素の数が 0: Nothing
・要素の数が 1:その要素の値
・要素の数が複数:各要素の値の配列(Object 型の配列)
objectCategory プロパティは値が 1つです。objectClass プロパティは値が複数あります。
値が複数ある場合はその中の 1つでも検索フィルタに引っ掛かれば抽出されるようです。
例えばコンピュータの objectClass プロパティの値の 1つに “Group" という値が格納されていたら、グループのリストを取得しようと思って "(objectClass=Group)“ と指定すると、グループとコンピュータのリストが取得されてしまいます。
あと、組織単位(OU)を検索する場合は "(objectCategory=OrganizationalUnit)” 、共有フォルダを検索する場合は "(objectCategory=Volume)” と記述します。
また、名前を指定してユーザやグループを検索することもよくあると思います。
名前を指定するんだったらサンプル通り "(name=○○)” でえんちゃう?と。
確かに name プロパティはどの種類のオブジェクトにも存在するので いんですが問題はその値。
グループ、コンピュータ、組織単位、共有フォルダの場合はサンプル通りでOK。
ユーザの場合はアカウント名(ドメインユーザのプロパティで「アカウント」タブにある「ユーザー ログオン名」)を指定するのが確実。(アカウント名は一意だから)
その場合は "(sAMAccountName=○○)” 。
いろいろ検索パターンを設けるなら、次のプロパティで検索できるよう実装するのもいいかも。
・cn:管理ツール上で表示される名前(識別名:Common Name)
・sn:姓(Surname)
・givenName:名
・mail:電子メール
・physicalDeliveryOfficeName:事業所
プリンタの場合は実装次第かなと。
name プロパティなら一意なので特定できますが、値が「サーバ名-プリンタ名」なのでサーバ名とプリンタ名を条件にするのと同じになってしまいます。
printerName プロパティだと複数取得される可能性があります。
まぁドメインの規模によりますが次のようにサーバ名とプリンタ名を別々に指定するのが無難かなと思います。(任意なら「*」を指定すればいいので)
"(&(shortServerName=○○)(printerName=○○))”
クエリの実行については、複数の結果を取得する場合は DirectorySearcher.FindAll メソッドを、1つだけ取得する場合は DirectorySearcher.FindOne メソッドを呼びます。
検索結果の DirectoryEntry は SearchResult.GetDirectoryEntry メソッドで取得します。
ローカルの場合はルートの DirectoryEntry.Children プロパティに対して処理します。
おまけ
Excel でセルの書式設定ダイアログを表示する時は Ctrl + 1。(テンキーの 1 じゃダメ)