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_QUERYTOKEN_DUPLICATETOKEN_ASSIGN_PRIMARY 访问权限。 有关详细信息,请参阅 Access-Token 对象的访问权限。 令牌表示的用户必须具有对 lpApplicationNamelpCommandLine 参数指定的应用程序的读取和执行访问权限。

若要获取表示指定用户的主令牌,请调用 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 个字符。 如果 lpApplicationNameNULL,则 lpCommandLine 的模块名称部分限制为 MAX_PATH 个字符。

此函数的 Unicode 版本 CreateProcessAsUserW 可以修改此字符串的内容。 因此,此参数不能是指向只读内存 (的指针,例如 const 变量或文本字符串) 。 如果此参数是常量字符串,则函数可能会导致访问冲突。

lpCommandLine 参数可以为 NULL。 在这种情况下,函数使用 lpApplicationName 指向的字符串作为命令行。

如果 lpApplicationNamelpCommandLine 均为非 NULL,则 *lpApplicationName 指定要执行的模块,*lpCommandLine 指定命令行。 新进程可以使用 GetCommandLine 检索整个命令行。 用 C 编写的控制台进程可以使用 argcargv 参数来分析命令行。 由于 argv[0] 是模块名称,因此 C 程序员通常会将模块名称重复为命令行中的第一个标记。

如果 lpApplicationNameNULL,则命令行的第一个空格分隔标记将指定模块名称。 如果使用包含空格的长文件名,请使用带引号的字符串来指示文件名结束的位置和参数开始的位置 (请参阅 ) lpApplicationName 参数的说明。 如果文件名不包含扩展名,则追加.exe。 因此,如果文件扩展名为 .com,则此参数必须包含 .com 扩展名。 如果文件名以无扩展名 (.) 句点结尾,或者文件名包含路径,则不追加.exe。 如果文件名不包含目录路径,系统会按以下顺序搜索可执行文件:

  1. 从中加载应用程序的目录。
  2. 父进程的当前目录。
  3. 32 位 Windows 系统目录。 使用 GetSystemDirectory 函数获取此目录的路径。
  4. 16 位 Windows 系统目录。 没有获取此目录路径的函数,但会对其进行搜索。
  5. Windows 目录。 使用 GetWindowsDirectory 函数获取此目录的路径。
  6. PATH 环境变量中列出的目录。 请注意,此函数不会搜索应用程序路径注册表项指定的每个 应用程序路径 。 若要在搜索序列中包含此每个应用程序的路径,请使用 ShellExecute 函数。
系统会向命令行字符串添加一个空字符,以将文件名与参数分开。 这会将原始字符串划分为两个字符串以供内部处理。

[in, optional] lpProcessAttributes

指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新进程对象的安全描述符,并确定子进程是否可以将返回的句柄继承给进程。 如果 lpProcessAttributesNULLlpSecurityDescriptorNULL,则进程将获取默认安全描述符,并且无法继承句柄。 默认的安全描述符是 hToken 参数中引用的用户。 此安全描述符可能不允许调用方访问,在这种情况下,进程在运行后可能不会再次打开。 进程句柄有效,并且将继续具有完全访问权限。

[in, optional] lpThreadAttributes

指向 SECURITY_ATTRIBUTES 结构的指针,该结构指定新线程对象的安全描述符,并确定子进程是否可以将返回的句柄继承给线程。 如果 lpThreadAttributesNULLlpSecurityDescriptorNULL,则线程将获取默认的安全描述符,并且无法继承句柄。 默认的安全描述符是 hToken 参数中引用的用户。 此安全描述符可能不允许调用方访问。

[in] bInheritHandles

如果此参数为 TRUE,则调用进程中的每个可继承句柄都由新进程继承。 如果 参数为 FALSE,则不继承句柄。 请注意,继承的句柄与原始句柄具有相同的值和访问权限。 有关可继承句柄的其他讨论,请参阅备注。

终端服务: 不能跨会话继承句柄。 此外,如果此参数为 TRUE,则必须在调用方所在的会话中创建进程。

受保护的流程灯 (PPL) 进程: 当 PPL 进程创建非 PPL 进程时,将阻止泛型句柄继承,因为不允许从非 PPL 进程到 PPL 进程PROCESS_DUP_HANDLE。 请参阅 进程安全性和访问权限

[in] dwCreationFlags

控制优先级类和进程创建的标志。 有关值的列表,请参阅 进程创建标志

此参数还控制新进程的优先级类,该类用于确定进程线程的计划优先级。 有关值的列表,请参阅 GetPriorityClass。 如果未指定任何优先级类标志,则优先级类默认为 NORMAL_PRIORITY_CLASS ,除非创建过程的优先级类 IDLE_PRIORITY_CLASSBELOW_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

指向 STARTUPINFOSTARTUPINFOEX 结构的指针。

用户必须对指定的窗口工作站和桌面具有完全访问权限。 如果希望进程是交互式的,请指定 winsta0\default。 如果 lpDesktop 成员为 NULL,则新进程将继承其父进程的桌面和窗口工作站。 如果此成员是空字符串“”,则新进程将使用进程连接到窗口工作站中所述的规则 连接到窗口工作站

若要设置扩展属性,请使用 STARTUPINFOEX 结构,并在 dwCreationFlags 参数中指定EXTENDED_STARTUPINFO_PRESENT

当不再需要 STARTUPINFOSTARTUPINFOEX 中的句柄时,必须使用 CloseHandle 关闭这些句柄。

重要 调用方负责确保 STARTUPINFO 中的标准句柄字段包含有效的句柄值。 即使 dwFlags 成员指定了STARTF_USESTDHANDLES,这些字段也会保持不变地复制到子进程 ,而无需验证。 不正确的值可能会导致子进程出现错误或崩溃。 使用 应用程序验证程序 运行时验证工具检测无效句柄。
 

[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 中指定它。

CreateProcessWithLogonWCreateProcessWithTokenW 函数类似于 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

请参阅

CloseHandle

CreateEnvironmentBlock

CreateProcess

CreateProcessWithLogonW

ExitProcess

GetEnvironmentStrings

GetExitCodeProcess

GetStartupInfo

ImpersonateLoggedOnUser

LoadUserProfile

PROCESS_INFORMATION

进程和线程函数

进程

SECURITY_ATTRIBUTES

SHCreateProcessAsUserW

STARTUPINFO

STARTUPINFOEX

SetErrorMode

WaitForInputIdle