CreateProcessWithLogonW 函数 (winbase.h)
创建新进程及其主线程。 然后,新进程在指定凭据的安全上下文中运行指定的可执行文件, (用户、域和密码) 。 可以选择加载指定用户的用户配置文件。
此函数类似于 CreateProcessAsUser 和 CreateProcessWithTokenW 函数,只是调用方不需要调用 LogonUser 函数即可对用户进行身份验证并获取令牌。
语法
BOOL CreateProcessWithLogonW(
[in] LPCWSTR lpUsername,
[in, optional] LPCWSTR lpDomain,
[in] LPCWSTR lpPassword,
[in] DWORD dwLogonFlags,
[in, optional] LPCWSTR lpApplicationName,
[in, out, optional] LPWSTR lpCommandLine,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCWSTR lpCurrentDirectory,
[in] LPSTARTUPINFOW lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
参数
[in] lpUsername
用户的名称。 这是要登录的用户帐户的名称。 如果使用 UPN 格式、 用户@DNS_domain_name, 则 lpDomain 参数必须为 NULL。
用户帐户必须具有本地计算机上的“本地登录”权限。 此权限授予工作站和服务器上的所有用户,但仅授予域控制器上的管理员。
[in, optional] lpDomain
其帐户数据库包含 lpUsername 帐户的域或服务器的名称。 如果此参数为 NULL,则必须以 UPN 格式指定用户名。
[in] lpPassword
lpUsername 帐户的明文密码。
[in] dwLogonFlags
登录选项。 此参数可以为 0 (零) 或以下值之一。
[in, optional] lpApplicationName
要执行的模块的名称。 此模块可以是基于 Windows 的应用程序。 它可以是某种其他类型的模块 (例如 MS-DOS 或 OS/2) (如果本地计算机上提供了相应的子系统)。
字符串可以指定要执行的模块的完整路径和文件名,也可以指定部分名称。 如果它是部分名称,则函数使用当前驱动器和当前目录来完成规范。 函数不使用搜索路径。 此参数必须包含文件扩展名;不假定默认扩展。
lpApplicationName 参数可以为 NULL,模块名称必须是 lpCommandLine 字符串中第一个空格分隔的标记。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名结束和参数开始的位置;否则,文件名不明确。
例如,可以通过不同的方式解释以下字符串:
“c:\program files\sub dir\program name”
系统尝试按以下顺序解释可能性:
- c:\program.exe files\sub dir\program name
- c:\program files\sub.exe dir\program name
- c:\program files\sub dir\program.exe name
- c:\program files\sub dir\program name.exe
如果可执行模块是 16 位应用程序, lpApplicationName 应为 NULL, 并且 lpCommandLine 指向的字符串应指定可执行模块及其参数。
[in, out, optional] lpCommandLine
要执行的命令行。 此字符串的最大长度为 1024 个字符。 如果 lpApplicationName 为 NULL,则 lpCommandLine 的模块名称部分限制为 MAX_PATH 个字符。
函数可以修改此字符串的内容。 因此,此参数不能是指向只读内存 (的指针,例如 const 变量或文本字符串) 。 如果此参数是常量字符串,则函数可能会导致访问冲突。
lpCommandLine 参数可以为 NULL,函数使用 lpApplicationName 指向的字符串作为命令行。
如果 lpApplicationName 和 lpCommandLine 均为非 NULL,则 *lpApplicationName 指定要执行的模块,*lpCommandLine 指定命令行。 新进程可以使用 GetCommandLine 检索整个命令行。 用 C 编写的控制台进程可以使用 argc 和 argv 参数来分析命令行。 由于 argv[0] 是模块名称,因此 C 程序员通常会将模块名称重复为命令行中的第一个标记。
如果 lpApplicationName 为 NULL,则命令行的第一个空格分隔标记将指定模块名称。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名结束的位置和参数开始的位置 (请参阅 ) lpApplicationName 参数的说明。 如果文件名不包含扩展名,则追加 .exe。 因此,如果文件扩展名.com,则此参数必须包含.com扩展名。 如果文件名以没有扩展名的句点结尾,或者文件名包含路径,则不追加 .exe。 如果文件名不包含目录路径,系统会按以下顺序搜索可执行文件:
- 从中加载应用程序的目录。
- 父进程的当前目录。
- 32 位 Windows 系统目录。 使用 GetSystemDirectory 函数获取此目录的路径。
- 16 位 Windows 系统目录。 没有获取此目录路径的函数,但会对其进行搜索。
- Windows 目录。 使用 GetWindowsDirectory 函数获取此目录的路径。
- PATH 环境变量中列出的目录。 请注意,此函数不会搜索由应用路径注册表项指定的每个 应用程序路径 。 若要在搜索序列中包含此每个应用程序的路径,请使用 ShellExecute 函数。
[in] dwCreationFlags
控制如何创建进程的标志。 默认情况下, CREATE_DEFAULT_ERROR_MODE、 CREATE_NEW_CONSOLE和 CREATE_NEW_PROCESS_GROUP 标志处于启用状态。 有关值的列表,请参阅 进程创建标志。
此参数还控制新进程的优先级类,该类用于确定进程线程的计划优先级。 有关值的列表,请参阅 GetPriorityClass。 如果未指定任何优先级类标志,则优先级类默认为 NORMAL_PRIORITY_CLASS ,除非创建过程的优先级类 IDLE_PRIORITY_CLASS 或 BELOW_NORMAL_PRIORITY_CLASS。 在这种情况下,子进程接收调用进程的默认优先级类。
如果 dwCreationFlags 参数的值为 0:
- 进程获取默认错误模式,创建新的控制台并创建新的进程组。
- 假定新进程的环境块包含 ANSI 字符, (请参阅 lpEnvironment 参数,了解) 的其他信息。
- 基于 16 位 Windows 的应用程序 (VDM) 在共享的虚拟 DOS 计算机中运行。
[in, optional] lpEnvironment
指向新进程的环境块的指针。 如果此参数为 NULL,则新进程使用根据 lpUsername 指定的用户的配置文件创建的环境。
环境块由以 null 结尾的字符串组成的以 null 结尾的块组成。 每个字符串采用以下形式:
名字=价值
由于等号 (=) 用作分隔符,因此不得将其用于环境变量的名称中。
环境块可以包含 Unicode 或 ANSI 字符。 如果 lpEnvironment 指向的环境块包含 Unicode 字符,请确保 dwCreationFlags 包含 CREATE_UNICODE_ENVIRONMENT。
ANSI 环境块以两个 0 (零个字节) 字节终止:一个用于最后一个字符串,再一个用于终止块。 Unicode 环境块以 4 个零字节结尾:两个字节作为最后一个字符串,另外两个用于终止该块。
若要检索特定用户的环境块的副本,请使用 CreateEnvironmentBlock 函数。
[in, optional] lpCurrentDirectory
进程当前目录的完整路径。 字符串还可以指定 UNC 路径。
如果此参数为 NULL,则新进程具有与调用进程相同的当前驱动器和目录。 此功能主要用于需要启动应用程序并指定其初始驱动器和工作目录的 shell。
[in] lpStartupInfo
指向 STARTUPINFO 结构的指针。
应用程序必须将指定用户帐户的权限添加到指定的窗口工作站和桌面,甚至对于 WinSta0\Default 也是如此。
如果 lpDesktop 成员为 NULL 或空字符串,则新进程将继承其父进程的桌面和窗口工作站。 应用程序必须将指定用户帐户的权限添加到继承的窗口工作站和桌面。
Windows XP: CreateProcessWithLogonW 将指定用户帐户的权限添加到继承的窗口工作站和桌面。
当不再需要 STARTUPINFO 中的句柄时,必须使用 CloseHandle 将其关闭。
[out] lpProcessInformation
指向 PROCESS_INFORMATION 结构的指针,该结构接收新进程的标识信息,包括进程的句柄。
当不需要时,必须使用 CloseHandle 函数关闭PROCESS_INFORMATION中的句柄。
返回值
如果该函数成功,则返回值为非零值。
如果函数失败,则返回值为 0(零)。 要获得更多的错误信息,请调用 GetLastError。
请注意,函数在进程完成初始化之前返回 。 如果找不到所需的 DLL 或无法初始化,则进程将终止。 若要获取进程的终止状态,请调用 GetExitCodeProcess。
注解
默认情况下, CreateProcessWithLogonW 不会将指定的用户配置文件加载到 HKEY_USERS 注册表项中。 这意味着访问 HKEY_CURRENT_USER 注册表项中的信息可能不会生成与正常交互式登录一致的结果。 在调用 CreateProcessWithLogonW 之前,你有责任使用 LOGON_WITH_PROFILE 或调用 LoadUserProfile 函数将用户注册表配置单元加载到 HKEY_USERS。
如果 lpEnvironment 参数为 NULL,则新进程使用根据 lpUserName 指定的用户的配置文件创建的环境块。 如果未设置 HOMEDRIVE 和 HOMEPATH 变量, CreateProcessWithLogonW 将修改环境块以使用用户工作目录的驱动器和路径。
创建新进程和线程句柄后, (PROCESS_ALL_ACCESS 和 THREAD_ALL_ACCESS) 接收完全访问权限。 对于任一句柄,如果未提供安全描述符,则可以在需要该类型对象句柄的任何函数中使用句柄。 提供安全描述符时,在授予访问权限之前,对句柄的所有后续使用执行访问检查。 如果拒绝访问,则请求进程无法使用句柄获取对进程或线程的访问权限。
若要检索安全令牌,请将 PROCESS_INFORMATION 结构中的进程句柄传递给 OpenProcessToken 函数。
为进程分配一个进程标识符。 标识符在进程终止之前有效。 它可用于标识进程,也可以在 OpenProcess 函数中指定它以打开进程的句柄。 进程中的初始线程也分配有一个线程标识符。 可以在 OpenThread 函数中指定它以打开线程的句柄。 标识符在线程终止之前有效,可用于唯一标识系统中的线程。 这些标识符在 PROCESS_INFORMATION 中返回。
调用线程可以使用 WaitForInputIdle 函数等待,直到新进程完成初始化并等待用户输入,而没有挂起的输入。 这对于父进程和子进程之间的同步非常有用,因为 CreateProcessWithLogonW 返回时不会等待新进程完成初始化。 例如,创建进程会在尝试查找与新进程关联的窗口之前使用 WaitForInputIdle 。
关闭进程的首选方法是使用 ExitProcess 函数,因为此函数会向附加到进程的所有 DLL 发送即将终止的通知。 关闭进程的其他方式不会通知附加的 DLL。 请注意,当线程调用 ExitProcess 时,进程的其他线程将终止,没有机会执行任何其他代码 (包括附加 DLL 的线程终止代码) 。 有关详细信息,请参阅 终止进程。
CreateProcessWithLogonW 在目标用户的安全上下文中访问指定的目录和可执行映像。 如果可执行映像位于网络上,并且路径中指定了网络驱动器号,则网络驱动器号对目标用户不可用,因为每次登录都可以分配网络驱动器号。 如果指定了网络驱动器号,则此函数将失败。 如果可执行映像位于网络上,请使用 UNC 路径。
此函数可以创建并同时运行的子进程数有限制。 例如,在 Windows XP 上,此限制为 MAXIMUM_WAIT_OBJECTS*4。 但是,由于系统范围的配额限制,可能无法创建如此多的进程。
具有 SP2、Windows Server 2003 或更高版本的 Windows XP: 不能从“LocalSystem”帐户下运行的进程调用 CreateProcessWithLogonW ,因为该函数在调用方令牌中使用登录 SID,并且“LocalSystem”帐户的令牌不包含此 SID。 作为替代方法,请使用 CreateProcessAsUser 和 LogonUser 函数。
若要编译使用此函数的应用程序, 请将_WIN32_WINNT 定义为 0x0500 或更高版本。 有关详细信息,请参阅 使用 Windows 标头。
安全备注
lpApplicationName 参数可以为 NULL,可执行文件名称必须是 lpCommandLine 中第一个空格分隔的字符串。 如果可执行文件或路径名称中有一个空格,则存在运行其他可执行文件的风险,因为函数分析空格的方式。 避免使用以下示例,因为该函数尝试运行“Program.exe”(如果存在),而不是“MyApp.exe”。LPTSTR szCmdline[]=_tcsdup(TEXT("C:\\Program Files\\MyApp"));
CreateProcessWithLogonW(..., szCmdline, ...)
如果恶意用户在系统上创建名为“Program.exe”的应用程序,则使用 Program Files 目录错误地调用 CreateProcessWithLogonW 的任何程序将运行恶意用户应用程序,而不是预期的应用程序。
若要避免此问题,请不要为 lpApplicationName 传递 NULL。 如果为 lpApplicationName 传递 NULL,请在 lpCommandLine 中的可执行路径周围使用引号,如以下示例所示:
LPTSTR szCmdline[]=_tcsdup(TEXT("\"C:\\Program Files\\MyApp\""));
CreateProcessWithLogonW(..., szCmdline, ...)
示例
以下示例演示如何调用此函数。
#include <windows.h>
#include <stdio.h>
#include <userenv.h>
void DisplayError(LPWSTR pszAPI)
{
LPVOID lpvMessageBuffer;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&lpvMessageBuffer, 0, NULL);
//
//... now display this string
//
wprintf(L"ERROR: API = %s.\n", pszAPI);
wprintf(L" error code = %d.\n", GetLastError());
wprintf(L" message = %s.\n", (LPWSTR)lpvMessageBuffer);
//
// Free the buffer allocated by the system
//
LocalFree(lpvMessageBuffer);
ExitProcess(GetLastError());
}
void wmain(int argc, WCHAR *argv[])
{
DWORD dwSize;
HANDLE hToken;
LPVOID lpvEnv;
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
WCHAR szUserProfile[256] = L"";
si.cb = sizeof(STARTUPINFO);
if (argc != 4)
{
wprintf(L"Usage: %s [user@domain] [password] [cmd]", argv[0]);
wprintf(L"\n\n");
return;
}
//
// TO DO: change NULL to '.' to use local account database
//
if (!LogonUser(argv[1], NULL, argv[2], LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, &hToken))
DisplayError(L"LogonUser");
if (!CreateEnvironmentBlock(&lpvEnv, hToken, TRUE))
DisplayError(L"CreateEnvironmentBlock");
dwSize = sizeof(szUserProfile)/sizeof(WCHAR);
if (!GetUserProfileDirectory(hToken, szUserProfile, &dwSize))
DisplayError(L"GetUserProfileDirectory");
//
// TO DO: change NULL to '.' to use local account database
//
if (!CreateProcessWithLogonW(argv[1], NULL, argv[2],
LOGON_WITH_PROFILE, NULL, argv[3],
CREATE_UNICODE_ENVIRONMENT, lpvEnv, szUserProfile,
&si, &pi))
DisplayError(L"CreateProcessWithLogonW");
if (!DestroyEnvironmentBlock(lpvEnv))
DisplayError(L"DestroyEnvironmentBlock");
CloseHandle(hToken);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
要求
要求 | 值 |
---|---|
最低受支持的客户端 | Windows XP [仅限桌面应用] |
最低受支持的服务器 | Windows Server 2003 [仅限桌面应用] |
目标平台 | Windows |
标头 | winbase.h (包括 Windows.h) |
Library | Advapi32.lib |
DLL | Advapi32.dll |