Active Directory Service Interface

Active Directory Service Interface (ADSI) 是一组植基于 COM 技术上的应用程序开发界面，程式开发人员可以利用这些界面来连接并存取 Active Directory，并执行查询，更新或删除等管理功能，ADSI同时可支援以LDAP（轻量级目录存取协定）为主的目录服务（例如Novell Directory Service），以及以 Windows NT 网域为主所组成的 WinNT 网域目录。

## 界面结构

ADSI 由 COM 以及原生函式库所组成，并封装在 activeds.dll 档案中，部分则被封装在 advaip32.dll 中，以 COM 原生界面方式呈现，以提供给 C/C++ 程式语言透过 COM 界面来取用，不过由于所有的原生界面均支援 COM Automation，因此 Visual Basic 或是 .NET Framework 的语言也可以利用 COM Interop 能力存取 ADSI 中的界面。

### 原生界面 (Native interfaces)

activeds.dll 中封装了几个 ADSI 中最核心的界面：

- IADs：作为所有 Active Directory 物件最顶层的界面，拥有最基础的功能。
- IADsContainer：作为所有可收纳 Active Directory 物件的收纳器 (Container) 最顶层的界面，具有收纳器的基础功能。
- IADsOpenDSObject：连接 Active Directory 之用（它只有 OpenDSObject 一个方法），同时它会快取住用来连接的安全资讯内容。
- IADsNamespace：由目录服务提供者 (directory service provider) 提供，用来识别目录结构的命名空间之用。

activeds.dll 亦提供了几个依物件不同而实作的界面，像是：

- IADsComputer：提供目录服务中的电脑 (computer) 物件。
- IADsDomain：提供目录服务中的网域 (domain) 物件。
- IADsUser：提供目录服务中的使用者 (user) 物件。
- IADsGroup：提供目录服务中的使用者群组 (group) 物件。
- IADsOU：提供目录服务中的组织单元 (organization unit) 物件。
- IADsCollection：提供目录服务物件集合。

在使用 ADSI 之时，有时会需要一些工具型功能，像是 DOMAIN\user 和 User Principal Name 间的转换，或是取得系统资讯等，ADSI 也提供了这类的界面：

- IADsNameTranslate：在目录服务中在不同的名称间转换。
- IADsWinNTSystemInfo：在目录服务中取得 Windows NT Directory Service 系统资讯。
- IADsADSystemInfo：在目录服务中取得 LDAP Directory Service 系统资讯。
- IADsPathname：剖析 X.500 名称与路径。

这些界面多是使用 C/C++ 程式语言来存取，若是使用 VB 或是 Scripting Language 时，则可以使用另外的方法来存取。

```vb
' Get user object from Active Directory.
Dim mach As IADsContainer
Dim usr as IADsUser

On Error GoTo Cleanup
Set mach = GetObject("WinNT://MyMachine,Computer")
Set usr = mach.Create("user","jeffsmith")
usr.SetInfo

Cleanup:
    If(Err.Number<>0) Then
        MsgBox("An error has occurred. " & Err.Number)
    End If
    Set mach = Nothing
    Set usr = Nothing
```

```cpp
// binding Active Directory using C++ and native interface.
IADs *pObject;
HRESULT hr;

// Initialize COM.
CoInitialize(NULL);

hr = ADsGetObject(L"LDAP://CN=jeffsmith,DC=fabrikam,DC=com", 
        IID_IADs,
        (void**) &pObject);
if(SUCCEEDED(hr))
{
    // Use the object.

    // Release the object.
    pObject->Release()
}

// Uninitialize COM.
CoUninitialize();
```

### 函式

ADSI 除了界面以外，也将部分功能使用函式方式显露，以简化呼叫原生界面需要处理的工作，较重要且经常被使用的有：

- ADsOpenDSObject，封装取得 IADs 或 IADsContainer 等物件的工作。
- ADsGetObject，封装利用已开启的 Active Directory Session 取得物件的工作。
- ADsGetLastError，取得在 ADSI 作业中，最后一个引发的错误。
- AllocADsMem, FreeADsMem, AllocADsStr, FreeADsStr，配置与释放 ADSI 使用的资源。
- ADsBuildEnumerator, ADsEnumerateNext, ADsFreeEnumerator：处理 ADSI 物件的列举工作。

下列的例子，是使用 ADsOpenDSObject 取得 IADs 物件的范例程式码：

```cpp
IADs *pObject;
LPWSTR szUsername = NULL;
LPWSTR szPassword = NULL
HRESULT hr;

// Insert code to securely retrieve the username and password.

hr = ADsOpenObject(L"LDAP://CN=Jeff,DC=Fabrikam,DC=com",
                   "jeffsmith",
                   "etercespot",
                   ADS_SECURE_AUTHENTICATION, 
                   IID_IADs,
                   (void**) &pObject);
```

### .NET Framework 原生界面

在 .NET Framework 中，另外提供了一个 .NET 专用的 ADSI 界面程式库 System.DirectoryService.dll，程式开发人员可以加入此程式库的参考来取得 ADSI 的功能，在这个原生类别库中，封装了两个类别，作为存取 Active Directory 的入口之用。

- DirectoryEntry：此类别封装了 IADs 以及 IADsContainer 等原生界面，用来存取 Active Directory 的物件资料，以及 Schema 封装的资讯等等。
- DirectorySearcher：此类别封装了 IDirectorySearch 界面，作为搜寻 Active Directory 的主要工具，并使用 SearchResult 物件封装搜寻结果，让 .NET 开发人员可以简化处理搜寻结果的工作。

截至 .NET Framework 3.5 为止，ADSI 在 Managed Class Library 中有四个命名空间：

- System.DirectoryServices：原生的 ADSI Managed 界面，封装 DirectoryEntry 和 DirectorySearcher 与工具类别。
- System.DirectoryServices.AccountManagement：封装对 IADsUser, IADsComputer, IADsGroup 等界面，简化处理物件的工具。
- System.DirectoryServices.ActiveDirectory：封装对 Active Directory 结构物件的工具。
- System.DirectoryServices.Protocol：封装对 LDAP 协定与目录服务通讯的工具与基础类别。

下列为使用 C# 存取 DirectoryEntry 的范例程式码：

```csharp
DirectoryEntry ent = new DirectoryEntry();
DirectoryEntry ou = ent.Children.Find("OU=Consulting");

// Use the Add method to add a user to an organizational unit.
DirectoryEntry usr = ou.Children.Add("CN=New User","user");
// Set the samAccountName, then commit changes to the directory.
usr.Properties["samAccountName"].Value = "newuser"; 
usr.CommitChanges();
```

## 目录服务提供者 (directory service provider)

ADSI 因为是使用了 LDAP 以及开放式目录等标准，因此只要目录服务的平台是以开放式目录标准实作 IADsNamespace 与其他目录服务提供者界面时，即可由 ADSI 来支援，目前 ADSI 有五种提供者：

- ADSI LDAP Provider：Active Directory 官方主要的支援界面，连接字串使用 LDAP:// 为主。
- ADSI WinNT Provider：以 Windows NT 为主的支援界面，连接字串使用 WinNT:// 为主。
- ADSI NDS Provider：支援 Novell Directory Service，连接字串使用 NDS:// 为主。
- ADSI NWCOMPAT Provider：支援 Novell Netware 3.x 的网络服务，连接字串使用 NWCOMPAT:// 为主。
- ADSI IIS Provider：支援 IIS 内部的提供者，连接字串以 IIS:// 为主，用于存取 IIS Metabase。

## OLE DB Provider for Directory Services

除了 ADSI 原生界面外，微软也为了 SQL Server 或是 ADO 应用程序开发人员发展了可使用 ADO 来存取 Active Directory 的驱动程式，此驱动程式使用 OLE DB 来开发，并且使用类似 SQL 指令的方式来存取 Active Directory，此驱动程式称为 OLE DB Provider for Directory Service，其 COM 的 Prog ID 为 ADsDSOObject。

下例为使用 ADO 存取 Active Directory 的范例程式码：

```vb
Dim Con As New Connection
Dim rs As New Recordset
Dim command As New Command
Dim usr As IADsUser

' Replace department for all users in OU=sales.
Set con = Server.CreateObject("ADODB.Connection")
con.Provider = "ADsDSOObject"
 
Set command = CreateObject("ADODB.Command")
Set command.ActiveConnection = con
 
command.CommandText = "SELECT AdsPath, cn FROM 'LDAP://OU=Sales,DC=Fabrikam,DC=com' WHERE objectClass = 'user'"
 
command.Properties("searchscope") = ADS_SCOPE_ONELEVEL
Set rs = command.Execute
While Not rs.EOF
    Set usr = GetObject(rs.Fields("AdsPath").Value)
    usr.Put "department", "1001"
    usr.SetInfo
    rs.MoveNext
Wend
```

下列指令为使用 SQL Server 查询来合并存取 Active Directory 的指令码：

```sql
-- create a linked server to active directory interface
EXEC sp_addlinkedserver 'ADSI', 'Active Directory Services 2.5', 'ADSDSOObject', 'adsdatasource' 

-- configure SQL Server to allow ad-hoc query and openquery statement.
EXEC sp_configure 'show advanced options', 1
reconfigure with override

EXEC sp_configure 'Ad Hoc Distributed Queries', 1 
reconfigure 

-- execute query via OpenQuery() statement
SELECT * FROM OpenQuery(ADSI, 'SELECT * FROM ''LDAP://DC=kodyaz,DC=com'' WHERE objectCategory=''user'' ') 

SELECT * FROM OpenQuery(ADSI, 'SELECT mail, displayName, userPrincipalName FROM ''LDAP://DC=kodyaz,DC=com'' WHERE objectCategory=''user'' ') 
```

## 开发 Directory-Enabled 应用程序时可用的工具

由于 ADSI 和 Active Directory 的特性，开发人员若没有使用适当的工具时，很难发展与目录服务连结 (directory-enabled) 的应用程序，因此微软为 Active Directory 提供了两种工具：

- Active Directory Explorer：由 Sysinternals 团队所开发，现已并入 Microsoft TechNet 团队中，可用来浏览 AD 网域或树系的 LDAP 阶层字串，以及物件的属性资料。
- ADSIEdit：在 Windows 2000 和 Windows Server 2003 时，由 Resource Kit Support Tools 安装包提供，到了 Windows Server 2008 时，则内建在系统中。

## 参考资料

内容参考：

文章参考：

- MSDN Library: Active Directory Service Interface
- Directory Service in .NET Framework