下面的代码示例演示在主计算机上安装启用了目录的服务的基本步骤。 它执行以下操作:
- 调用 OpenSCManager 函数以打开本地计算机上的服务控制管理器(SCM)的句柄。
- 调用 CreateService 函数以在 SCM 数据库中安装服务。 此调用指定服务的登录帐户和密码,以及服务的可执行文件以及有关该服务的其他信息。 如果指定的登录帐户无效,CreateService 将失败。 但是,CreateService 不会检查密码的有效性。 它也不会验证帐户在本地计算机上是否具有作为服务登录的权限。 有关详细信息,请参阅 在主计算机上授予登录即服务权限。
- 调用服务的 ScpCreate 子例程,该子例程在目录中创建服务连接点对象(SCP)以发布此服务实例的位置。 有关详细信息,请参阅客户端如何查找和使用服务连接点。 此例程还会将服务的绑定信息存储在 SCP 中,在 SCP 上设置 ACE,以便服务可以在运行时访问它,在本地注册表中缓存 SCP 的可分辨名称,并返回新 SCP 的可分辨名称。
- 调用服务的 SpnCompose 子例程,该子例程使用服务的类字符串和 SCP 的可分辨名称来构成服务主体名称(SPN)。 有关详细信息,请参阅 使用 SCP 为服务撰写 SPN。 SPN 唯一标识此服务的实例。
- 调用服务的 SpnRegister 子例程,该子例程在与服务的登录帐户关联的帐户对象上注册 SPN。 有关详细信息,请参阅 注册服务的 SPN。 SPN 的注册使客户端应用程序能够对服务进行身份验证。
无论登录帐户是本地用户帐户还是域用户帐户还是 LocalSystem 帐户,此代码示例都正常工作。 对于域用户帐户,szServiceAccountSAM 参数包含帐户的域**\**用户名,szServiceAccountDN 参数包含目录中用户帐户对象的可分辨名称。 对于 LocalSystem 帐户,szServiceAccountSAM 和 szPassword 为 NULL,szServiceAccountSN 是目录中本地计算机的帐户对象的可分辨名称。 如果 szServiceAccountSAM 指定本地用户帐户(名称格式为“.\UserName”),则代码示例将跳过 SPN 注册,因为本地用户帐户不支持相互身份验证。
请注意,默认安全配置仅允许域管理员执行此代码。
此外,请注意,此代码示例(如已编写)必须在安装服务的计算机上执行。 因此,它通常位于服务安装代码(如果有)的单独安装可执行文件中,用于扩展架构、扩展 UI 或设置组策略。 这些操作为整个林安装服务组件,而此代码在单个计算机上安装服务。
void InstallServiceOnLocalComputer(
            LPTSTR szServiceAccountDN,  // Distinguished name of logon account.
            LPTSTR szServiceAccountSAM, // SAM name of logon account.
            LPTSTR szPassword)          // Password of logon account.
{
SC_HANDLE   schService = NULL;
SC_HANDLE   schSCManager = NULL;
TCHAR szPath[512];
LPTSTR lpFilePart;
TCHAR szDNofSCP[MAX_PATH];
TCHAR szServiceClass[]=TEXT("ADSockAuth");
 
DWORD dwStatus;
TCHAR **pspn=NULL;
ULONG ulSpn=1;
 
// Get the full path of the service's executable.
// The code example assumes that the executable is in the current directory.
dwStatus = GetFullPathName(TEXT("service.exe"), 512, szPath, &lpFilePart);
if (dwStatus == 0) {
    _tprintf(TEXT("Unable to install %s - %s\n"), 
            TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256));
    return;
}
_tprintf(TEXT("path of service.exe: %s\n"), szPath);
 
// Open the Service Control Manager on the local computer.
schSCManager = OpenSCManager(
                NULL,                   // Computer (NULL == local)
                NULL,                   // Database (NULL == default)
                SC_MANAGER_ALL_ACCESS   // Access required
                );
if (! schSCManager) {
    _tprintf(TEXT("OpenSCManager failed - %s\n"), 
                   GetLastErrorText(szErr,256));
    goto cleanup;
}
        
// Install the service in the SCM database.
schService = CreateService(
            schSCManager,               // SCManager database
            TEXT(SZSERVICENAME),        // Name of service
            TEXT(SZSERVICEDISPLAYNAME), // Name to display
            SERVICE_ALL_ACCESS,         // Desired access
            SERVICE_WIN32_OWN_PROCESS,  // Service type
            SERVICE_DEMAND_START,       // Start type
            SERVICE_ERROR_NORMAL,       // Error control type
            szPath,                     // Service binary
            NULL,                       // No load ordering group
            NULL,                       // No tag identifier
            TEXT(SZDEPENDENCIES),       // Dependencies
            szServiceAccountSAM,        // Service account
            szPassword);                // Account password
if (! schService) {
    _tprintf(TEXT("CreateService failed - %s\n"), 
                   GetLastErrorText(szErr,256));
    goto cleanup;
}
 
_tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
 
// Create the service's Service Connection Point (SCP).
dwStatus = ScpCreate(
        2000,                 // Service default port number
        szServiceClass,       // Specifies the service class string
        szServiceAccountSAM,  // SAM name of logon account for ACE
        szDNofSCP             // Buffer returns the DN of the SCP
        );
if (dwStatus != 0) {
    _tprintf(TEXT("ScpCreate failed: %d\n"), dwStatus );
    DeleteService(schService);
    goto cleanup;
}
 
// Compose and register a service principal name for this service.
// This is performed on the install path because this requires elevated
// privileges for updating the directory.
// If a local account of the format ".\user name", skip the SPN.
if ( szServiceAccountSAM[0] == '.' ) 
{
    _tprintf(TEXT("Do not register SPN for a local account.\n"));
    goto cleanup;
}
 
dwStatus = SpnCompose(
        &pspn,            // Receives pointer to the SPN array.
        &ulSpn,           // Receives number of SPNs returned.
        szDNofSCP,        // Input: DN of the SCP.
        szServiceClass);  // Input: the service's class string.
 
if (dwStatus == NO_ERROR) 
    dwStatus = SpnRegister(
        szServiceAccountDN,  // Account on which SPNs are registered.
        pspn,                // Array of SPNs to register.
        ulSpn,               // Number of SPNs in array.
        DS_SPN_ADD_SPN_OP);  // Operation code: Add SPNs.
 
if (dwStatus != NO_ERROR) 
{
    _tprintf(TEXT("Failed to compose SPN: Error was %X\n"), 
                  dwStatus);
    DeleteService(schService);
    ScpDelete(szDNofSCP, szServiceClass, szServiceAccountDN);
    goto cleanup;
}
 
cleanup:
if (schSCManager)
    CloseServiceHandle(schSCManager);
if (schService)
    CloseServiceHandle(schService);
DsFreeSpnArray(ulSpn, pspn);
return;
}
有关前面的代码示例的详细信息,请参阅 使用 SCP 为服务编写 SPN 并 注册服务的 SPN。