CreateProcessAsUserA 函数(processthreadsapi.h)
创建一个新进程及其主线程。 新进程在由指定令牌表示的用户的安全上下文中运行。
通常,调用 CreateProcessAsUser 函数的进程必须具有 SE_INCREASE_QUOTA_NAME 权限,并且如果令牌不可分配,可能需要 SE_ASSIGNPRIMARYTOKEN_NAME 特权。 如果此函数失败并 ERROR_PRIVILEGE_NOT_HELD (1314),请改用 CreateProcessWithLogonW 函数。 CreateProcessWithLogonW 不需要特殊权限,但必须允许指定的用户帐户以交互方式登录。 通常,最好使用 CreateProcessWithLogonW 创建具有备用凭据的进程。
语法
BOOL CreateProcessAsUserA(
[in, optional] HANDLE hToken,
[in, optional] LPCSTR lpApplicationName,
[in, out, optional] LPSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCSTR lpCurrentDirectory,
[in] LPSTARTUPINFOA lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
参数
[in, optional] hToken
表示用户的主令牌的句柄。 句柄必须具有 TOKEN_QUERY、TOKEN_DUPLICATE和 TOKEN_ASSIGN_PRIMARY 访问权限。 有关详细信息,请参阅 Access-Token 对象的访问权限。 由令牌表示的用户必须具有对 lpApplicationName 或 lpCommandLine 参数指定的应用程序的读取和执行访问权限。
若要获取表示指定用户的主令牌,请调用 LogonUser 函数。 或者,可以调用 DuplicateTokenEx 函数,将模拟令牌转换为主令牌。 这允许模拟客户端的服务器应用程序创建具有客户端安全上下文的进程。
如果 hToken 是调用方主令牌的受限版本,则不需要 SE_ASSIGNPRIMARYTOKEN_NAME 特权。 如果尚未启用必要的权限,CreateProcessAsUser 在调用期间启用它们。 有关详细信息,请参阅 使用特殊特权运行。
终端服务:进程在令牌中指定的会话中运行。 默认情况下,这是调用 LogonUser的会话。 若要更改会话,请使用 SetTokenInformation 函数。
[in, optional] lpApplicationName
要执行的模块的名称。 此模块可以是基于 Windows 的应用程序。 如果本地计算机上提供了相应的子系统,它可以是某种其他类型的模块(例如,MS-DOS 或 OS/2)。
字符串可以指定要执行的模块的完整路径和文件名,也可以指定部分名称。 对于部分名称,该函数使用当前驱动器和当前目录来完成规范。 该函数不会使用搜索路径。 此参数必须包含文件扩展名;未假定默认扩展。
lpApplicationName 参数可以 NULL。 在这种情况下,模块名称必须是 lpCommandLine 字符串中的第一个空格分隔标记。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名的结尾和参数的开始位置;否则,文件名不明确。 例如,请考虑字符串“c:\program files\sub dir\program name”。 可以通过多种方式解释此字符串。 系统尝试按以下顺序解释可能性:
c:\program.exec:\program files\sub.exec:\program files\sub dir\program.exec:\program files\sub dir\program name.exe 如果可执行模块是 16 位应用程序,lpApplicationName 应 NULL,lpCommandLine 指向的字符串应指定可执行模块及其参数。 默认情况下,
[in, out, optional] lpCommandLine
要执行的命令行。 此字符串的最大长度为 32K 个字符。 如果 lpApplicationNameNULL,则 lpCommandLine 的模块名称部分限制为 MAX_PATH 字符。
此函数的 Unicode 版本(CreateProcessAsUserW)可以修改此字符串的内容。 因此,此参数不能是指向只读内存的指针(例如 常量 变量或文本字符串)。 如果此参数是常量字符串,则函数可能会导致访问冲突。
lpCommandLine 参数可以 NULL。 在这种情况下,该函数使用 lpApplicationName 指向的字符串作为命令行。
如果 lpApplicationName 和 lpCommandLine 都是非NULL,*lpApplicationName 指定要执行的模块,*lpCommandLine 指定命令行。 新进程可以使用 GetCommandLine 来检索整个命令行。 用 C 编写的控制台进程可以使用 argc 和 argv 参数分析命令行。 由于 argv[0] 是模块名称,因此 C 程序员通常会将模块名称重复为命令行中的第一个标记。
如果 lpApplicationNameNULL,则命令行的第一个空格分隔标记指定模块名称。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名的结尾和参数的开始位置(请参阅 lpApplicationName 参数的说明)。 如果文件名不包含扩展名,则追加 .exe。 因此,如果文件扩展名.com,此参数必须包含.com扩展名。 如果文件名以不带扩展名的句点 (.) 结尾,或者文件名包含路径,则不会追加 .exe。 如果文件名不包含目录路径,系统将按以下顺序搜索可执行文件:
- 从中加载应用程序的目录。
- 父进程的当前目录。
- 32 位 Windows 系统目录。 使用 GetSystemDirectory 函数获取此目录的路径。
- 16 位 Windows 系统目录。 没有获取此目录路径的函数,但搜索该函数。
- Windows 目录。 使用 GetWindowsDirectory 函数获取此目录的路径。
- PATH 环境变量中列出的目录。 请注意,此函数不会搜索 应用路径 注册表项指定的每个应用程序路径。 若要在搜索序列中包含此每个应用程序路径,请使用 ShellExecute 函数。
[in, optional] lpProcessAttributes
指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新进程对象的安全描述符,并确定子进程是否可以将返回的句柄继承给进程。 如果 lpProcessAttributesNULL 或 lpSecurityDescriptorNULL,则进程将获取默认的安全描述符,并且无法继承句柄。 默认安全描述符是 hToken 参数中引用的用户。 此安全描述符可能不允许调用方访问,在这种情况下,在运行进程后可能不会再次打开。 进程句柄有效,将继续具有完全访问权限。
[in, optional] lpThreadAttributes
指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新线程对象的安全描述符,并确定子进程是否可以将返回的句柄继承给线程。 如果 lpThreadAttributesNULL 或 lpSecurityDescriptor 为 NULL,则线程将获取默认的安全描述符,并且无法继承句柄。 默认安全描述符是 hToken 参数中引用的用户。 此安全描述符可能不允许调用方访问。
[in] bInheritHandles
如果此参数 TRUE,则调用进程中的每个可继承句柄都由新进程继承。 如果参数 FALSE,则不会继承句柄。 请注意,继承的句柄的值和访问权限与原始句柄相同。 有关可继承句柄的其他讨论,请参阅“备注”。
终端服务:不能跨会话继承句柄。 此外,如果此参数 TRUE,则必须在调用方所在的同一会话中创建进程。
受保护的进程灯(PPL)进程:PPL 进程创建非 PPL 进程时阻止泛型句柄继承,因为不允许将非 PPL 进程从非 PPL 进程到 PPL 进程PROCESS_DUP_HANDLE。 请参阅 处理安全和访问权限
[in] dwCreationFlags
控制优先级类和创建进程的标志。 有关值列表,请参阅 进程创建标志。
此参数还控制新进程的优先级类,该类用于确定进程的线程的计划优先级。 有关值列表,请参阅 GetPriorityClass。 如果未指定任何优先级类标志,则优先级类默认为 NORMAL_PRIORITY_CLASS,除非创建过程的优先级类 IDLE_PRIORITY_CLASS 或 BELOW_NORMAL_PRIORITY_CLASS。 在这种情况下,子进程接收调用进程的默认优先级类。
如果 dwCreationFlags 参数的值为 0:
- 进程继承调用方和父控制台的错误模式。
- 假定新进程的环境块包含 ANSI 字符(请参阅 lpEnvironment 参数了解其他信息)。
- 基于 Windows 的 16 位应用程序在共享虚拟 DOS 计算机(VDM)中运行。
[in, optional] lpEnvironment
指向新进程的环境块的指针。 如果此参数 NULL,则新进程使用调用进程的环境。
环境块由以 null 结尾的字符串的 null 终止块组成。 每个字符串采用以下形式:
名称=值\0
由于等号用作分隔符,因此不得在环境变量的名称中使用。
环境块可以包含 Unicode 或 ANSI 字符。 如果由 lpEnvironment 指向的环境块包含 Unicode 字符,请确保 dwCreationFlags 包含 CREATE_UNICODE_ENVIRONMENT。
如果进程的环境块总大小超过 32,767 个字符,则此函数的 ANSI 版本 CreateProcessAsUserA 失败。
请注意,ANSI 环境块由两个零字节终止:一个用于最后一个字符串,另一个用于终止该块。 Unicode 环境块由四个零字节终止:两个用于最后一个字符串,两个用于终止该块。
Windows Server 2003 和 Windows XP:如果合并的用户和系统环境变量的大小超过 8192 字节,则由 CreateProcessAsUser 创建的进程 不再与父进程传递给函数的环境块一起运行。 相反,子进程使用由 CreateEnvironmentBlock 函数返回的环境块运行。
若要检索给定用户的环境块的副本,请使用 CreateEnvironmentBlock 函数。
[in, optional] lpCurrentDirectory
进程的当前目录的完整路径。 该字符串还可以指定 UNC 路径。
如果此参数为 NULL,则新进程将具有与调用进程相同的当前驱动器和目录。 (此功能主要用于需要启动应用程序的 shell,并指定其初始驱动器和工作目录。
[in] lpStartupInfo
指向 STARTUPINFO 或 STARTUPINFOEX 结构的指针。
用户必须具有对指定窗口工作站和桌面的完全访问权限。 如果希望进程是交互式的,请指定 winsta0\default。 如果 lpDesktop 成员为 NULL,则新进程将继承其父进程的桌面和窗口工作站。 如果此成员是空字符串“”,则新进程将使用 进程连接到窗口工作站中所述的规则连接到窗口工作站。
若要设置扩展属性,请使用 STARTUPINFOEX 结构并在 dwCreationFlags 参数中指定 EXTENDED_STARTUPINFO_PRESENT。
STARTUPINFO 或 STARTUPINFOEX 中的句柄必须用 不再需要的 closeHandle 关闭。
[out] lpProcessInformation
指向接收有关新进程的标识信息的 PROCESS_INFORMATION 结构的指针。
PROCESS_INFORMATION 中的句柄必须用不再需要 CloseHandle 关闭。
返回值
如果函数成功,则返回值为非零。
如果函数失败,则返回值为零。 若要获取扩展的错误信息,请调用 GetLastError。
请注意,该函数在进程完成初始化之前返回。 如果所需的 DLL 无法找到或无法初始化,则进程将终止。 若要获取进程的终止状态,请调用 GetExitCodeProcess。
言论
CreateProcessAsUser 必须能够使用 TOKEN_DUPLICATE 和 TOKEN_IMPERSONATE 访问权限打开调用进程的主令牌。
默认情况下,CreateProcessAsUser 在具有不可见且无法接收用户输入的桌面的非交互窗口工作站上创建新进程。 若要启用与新进程的用户交互,必须在 STARTUPINFO 结构的 lpDesktop 成员中指定默认交互式窗口工作站和桌面“winsta0\default”的名称。 此外,在调用 CreateProcessAsUser之前,必须更改默认交互式窗口工作站和默认桌面的任意访问控制列表(DACL)。 窗口工作站和桌面的 DACL 必须授予对用户或由 hToken 参数表示的登录会话的访问权限。
CreateProcessAsUser 不会将指定的用户配置文件加载到 HKEY_USERS 注册表项中。 因此,若要访问 HKEY_CURRENT_USER 注册表项中的信息,必须先使用 LoadUserProfile 函数将用户配置文件信息加载到 HKEY_USERS 中,然后才能调用 CreateProcessAsUser。 在新进程退出后,请务必调用 UnloadUserProfile。
如果 lpEnvironment 参数为 NULL,则新进程将继承调用进程的环境。 CreateProcessAsUser 不会自动修改环境块,以包含特定于由 hToken表示的用户的环境变量。 例如,如果 lpEnvironment 为 NULL,则从调用进程继承 USERNAME 和 USERDOMAIN 变量。 你有责任为新进程准备环境块,并在 lpEnvironment中指定它。
CreateProcessWithLogonW 和 CreateProcessWithTokenW 函数 类似于 createProcessAsUser,但调用方无需调用 LogonUser 函数来验证用户并获取令牌。
CreateProcessAsUser 允许在调用方或目标用户的安全上下文中访问指定的目录和可执行映像。 默认情况下,CreateProcessAsUser 访问调用方的安全上下文中的目录和可执行映像。 在这种情况下,如果调用方无权访问目录和可执行映像,该函数将失败。 若要使用目标用户的安全上下文访问目录和可执行映像,请在调用 ImpersonateLoggedOnUser 函数之前指定 hToken,然后再调用 CreateProcessAsUser。
为进程分配了一个进程标识符。 标识符在进程终止之前有效。 它可用于标识进程,也可以在 OpenProcess 函数中指定以打开进程的句柄。 进程中的初始线程也分配了线程标识符。 可以在 OpenThread 函数中指定它,以打开线程的句柄。 标识符在线程终止之前有效,可用于唯一标识系统中的线程。 这些标识符在 PROCESS_INFORMATION 结构中返回。
调用线程可以使用 WaitForInputIdle 函数等待,直到新进程完成其初始化,并且正在等待用户输入,且没有输入挂起。 这对于父进程和子进程之间的同步非常有用,因为 CreateProcessAsUser 返回,而无需等待新进程完成其初始化。 例如,在尝试查找与新进程关联的窗口之前,创建过程将使用 WaitForInputIdle。
关闭进程的首选方法是使用 ExitProcess 函数,因为此函数将接近终止的通知发送到附加到进程的所有 DLL。 关闭进程的其他方法不会通知附加的 DLL。 请注意,当线程调用 ExitProcess时,进程的其他线程将终止,而无需执行任何其他代码(包括附加 DLL 的线程终止代码)。 有关详细信息,请参阅 终止进程。
默认情况下,将 TRUE 作为 bInheritHandles 参数的值传递会导致新进程继承所有可继承的句柄。 对于同时从多个线程创建进程但希望每个进程继承不同的句柄的应用程序来说,这可能会造成问题。 应用程序可以将 UpdateProcThreadAttributeList 函数与 PROC_THREAD_ATTRIBUTE_HANDLE_LIST 参数一起使用,以提供要由特定进程继承的句柄列表。
安全备注
lpApplicationName 参数可以为 NULL,在这种情况下,可执行文件名称必须是 lpCommandLine中的第一个空格分隔字符串。 如果可执行文件或路径名称中有一个空格,则由于函数分析空格的方式,可能会运行不同的可执行文件。 下面的示例很危险,因为该函数将尝试运行“Program.exe”(如果存在)而不是“MyApp.exe”。 LPTSTR szCmdline[] = _tcsdup(TEXT("C:\\Program Files\\MyApp"));
CreateProcessAsUser(hToken, NULL, szCmdline, /*...*/ );
如果恶意用户在系统上创建名为“Program.exe”的应用程序,则使用 Program Files 目录错误地调用 CreateProcessAsUser 的任何程序都将运行此应用程序,而不是预期应用程序。
为了避免此问题,请不要为 lpApplicationName传递 NULL。 如果为 lpApplicationName传递 NULL,请使用 lpCommandLine中可执行文件路径的引号,如以下示例所示。
LPTSTR szCmdline[] = _tcsdup(TEXT("\"C:\\Program Files\\MyApp\""));
CreateProcessAsUser(hToken, NULL, szCmdline, /*...*/);
PowerShell:使用 CreateProcessAsUser 函数在 PowerShell 版本 2.0 中实现 cmdlet 时,该 cmdlet 可针对扇入和扇出远程会话正确运行。 但是,由于某些安全方案,使用 CreateProcessAsUser 实现的 cmdlet 仅在 PowerShell 版本 3.0 中为扇入远程会话正常运行;由于客户端安全权限不足,扇出远程会话将失败。 若要实现适用于 PowerShell 版本 3.0 中扇入和扇出远程会话的 cmdlet,请使用 CreateProcess 函数。
例子
有关示例,请参阅 启动交互式客户端进程。
注意
processthreadsapi.h 标头将 CreateProcessAsUser 定义为一个别名,该别名根据 UNICODE 预处理器常量的定义自动选择此函数的 ANSI 或 Unicode 版本。 将中性编码别名与不中性编码的代码混合使用可能会导致编译或运行时错误不匹配。 有关详细信息,请参阅函数原型的
要求
要求 | 价值 |
---|---|
最低支持的客户端 | Windows XP [仅限桌面应用] |
支持的最低服务器 | Windows Server 2003 [仅限桌面应用] |
目标平台 | 窗户 |
标头 | processthreadsapi.h (包括 Windows.h) |
库 | Advapi32.lib |
DLL | Advapi32.dll |