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 指向的字符串应指定可执行模块及其参数。 默认情况下,CreateProcessAsUser 创建的所有基于 Windows 的 16 位应用程序在单独的 VDM (中运行,等效于 CreateProcess) 中的CREATE_SEPARATE_WOW_VDM。
[in, out, optional] lpCommandLine
要执行的命令行。 此字符串的最大长度为 32K 个字符。 如果 lpApplicationName 为 NULL,则 lpCommandLine 的模块名称部分限制为 MAX_PATH 个字符。
此函数的 Unicode 版本 CreateProcessAsUserW 可以修改此字符串的内容。 因此,此参数不能是指向只读内存 (的指针,例如 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, optional] lpProcessAttributes
指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新进程对象的安全描述符,并确定子进程是否可以将返回的句柄继承给进程。 如果 lpProcessAttributes 为 NULL 或 lpSecurityDescriptor 为 NULL,则进程将获取默认安全描述符,并且无法继承句柄。 默认安全描述符是在 hToken 参数中引用的用户。 此安全描述符可能不允许调用方访问,在这种情况下,进程在运行后可能不会再次打开。 进程句柄有效,并且将继续具有完全访问权限。
[in, optional] lpThreadAttributes
指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新线程对象的安全描述符,并确定子进程是否可以将返回的句柄继承给线程。 如果 lpThreadAttributes 为 NULL 或 lpSecurityDescriptor 为 NULL,则线程将获取默认安全描述符,并且无法继承句柄。 默认安全描述符是在 hToken 参数中引用的用户。 此安全描述符可能不允许调用方访问。
[in] bInheritHandles
如果此参数为 TRUE,则调用进程中的每个可继承句柄都由新进程继承。 如果参数为 FALSE,则不继承句柄。 请注意,继承的句柄与原始句柄具有相同的值和访问权限。 有关可继承句柄的其他讨论,请参阅备注。
终端服务: 不能跨会话继承句柄。 此外,如果此参数为 TRUE,则必须在调用方所在的同一会话中创建进程。
受保护的流程灯 (PPL) 进程: 当 PPL 进程创建非 PPL 进程时,将阻止泛型句柄继承,因为不允许将PROCESS_DUP_HANDLE从非 PPL 进程转换为 PPL 进程。 请参阅 进程安全性和访问权限
[in] dwCreationFlags
控制优先级类和进程的创建的标志。 有关值的列表,请参阅 进程创建标志。
此参数还控制新进程的优先级类,该类用于确定进程线程的计划优先级。 有关值的列表,请参阅 GetPriorityClass。 如果未指定任何优先级类标志,则优先级类默认为 NORMAL_PRIORITY_CLASS ,除非创建过程的优先级类 IDLE_PRIORITY_CLASS 或 BELOW_NORMAL_PRIORITY_CLASS。 在这种情况下,子进程接收调用进程的默认优先级类。
如果 dwCreationFlags 参数的值为 0:
- 进程同时继承调用方和父级控制台的错误模式。
- 假定新进程的环境块包含 ANSI 字符 (请参阅 lpEnvironment 参数以获取) 的其他信息。
- 基于 16 位 Windows 的应用程序 (VDM) 在共享虚拟 DOS 计算机中运行。
[in, optional] lpEnvironment
指向新进程的环境块的指针。 如果此参数为 NULL,则新进程将使用调用进程的环境。
环境块由以 null 结尾的字符串组成的以 null 结尾的块组成。 每个字符串采用以下形式:
名字=value\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注册表项中的信息,必须在调用 CreateProcessAsUser 之前,使用 LoadUserProfile 函数将用户配置文件信息加载到HKEY_USERS中。 请确保在新进程退出后调用 UnloadUserProfile 。
如果 lpEnvironment 参数为 NULL,则新进程将继承调用进程的环境。 CreateProcessAsUser 不会自动修改环境块,以包含特定于 由 hToken 表示的用户的环境变量。 例如,如果 lpEnvironment 为 NULL,则从调用进程继承 USERNAME 和 USERDOMAIN 变量。 你有责任为新进程准备环境块,并在 lpEnvironment 中指定它。
CreateProcessWithLogonW 和 CreateProcessWithTokenW 函数类似于 CreateProcessAsUser,只不过调用方无需调用 LogonUser 函数即可对用户进行身份验证并获取令牌。
CreateProcessAsUser 允许在调用方或目标用户的安全上下文中访问指定的目录和可执行映像。 默认情况下, CreateProcessAsUser 在调用方的安全上下文中访问目录和可执行映像。 在这种情况下,如果调用方无权访问目录和可执行映像,则函数将失败。 若要使用目标用户的安全上下文访问目录和可执行映像,请在调用 CreateProcessAsUser 之前,在调用 ImpersonateLoggedOnUser 函数时指定 hToken。
为进程分配一个进程标识符。 标识符在进程终止之前有效。 它可用于标识进程,或在 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 [仅限桌面应用] |
目标平台 | Windows |
标头 | processthreadsapi.h (包括 Windows.h) |
Library | Advapi32.lib |
DLL | Advapi32.dll |
另请参阅
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈