如何运行需要提升权限的子进程并等待?

35

Win 7/UAC让我烦透了。

在我的C++应用程序中,我需要运行一个在Windows 7上需要提权的可执行文件。我想启动它并等待它完成后再继续进行。有什么最简单的方法可以实现这一点吗?

通常,我会通过CreateProcess()来完成此类操作,但对于需要提权的可执行文件,它会失败。

我尝试通过CreateProcess运行cmd.exe /c ...,虽然可以运行,但会弹出一个难看的cmd终端窗口。

我了解到ShellExecute()将允许提权,但在使用ShellExecute()时似乎很难等待可执行文件完成。像system()这样简单的东西能不能使用呢?

如果还有其他想法,非常感谢!

3个回答

52
请使用 ShellExecuteEx,而不是 ShellExecute。这个函数会提供一个处理创建的进程的句柄,你可以使用它来调用 WaitForSingleObject 来阻塞直到该进程终止。最后,只需调用 CloseHandle 关闭进程句柄即可。

示例代码(为了清晰简洁省略了大部分错误检查):

SHELLEXECUTEINFO shExInfo = {0};
shExInfo.cbSize = sizeof(shExInfo);
shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shExInfo.hwnd = 0;
shExInfo.lpVerb = _T("runas");                // Operation to perform
shExInfo.lpFile = _T("C:\\MyApp.exe");       // Application to start    
shExInfo.lpParameters = "";                  // Additional parameters
shExInfo.lpDirectory = 0;
shExInfo.nShow = SW_SHOW;
shExInfo.hInstApp = 0;  

if (ShellExecuteEx(&shExInfo))
{
    WaitForSingleObject(shExInfo.hProcess, INFINITE);
    CloseHandle(shExInfo.hProcess);
}

指定 lpVerb 的“runas”动作是导致 UAC 提升即将启动的应用程序的原因。这相当于在应用程序清单中将权限级别设置为“requireAdministrator”。它将要求管理员和有限用户进行 UAC 提升。
但值得注意的是,除非绝对必要,否则应优先使用“标准”方法添加一个指定所需执行级别的应用程序清单来启动应用程序。如果您采用此方法,则只需传递“open”作为lpVerb。下面是一个示例清单:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <dependency>
                <dependentAssembly>
                        <assemblyIdentity
                                type="win32"
                                name="Microsoft.Windows.Common-Controls"
                                version="6.0.0.0"
                                processorArchitecture="X86"
                                publicKeyToken="6595b64144ccf1df"
                                language="*"
                        />
                </dependentAssembly>
        </dependency>
        <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
                <security>
                        <requestedPrivileges>
                                <requestedExecutionLevel 
                                       level="requireAdministrator" 
                                       uiAccess="false"/>
                        </requestedPrivileges>
                </security>
        </trustInfo>
</assembly>

最后,请确保在您的应用程序中触发需要UAC提升的进程的任何元素都被标记相应地。这是您在用户界面中建模的工作;Windows不会为您处理。这可以通过在入口点上显示盾牌图标来完成;例如:

        在按钮上显示UAC盾牌                                     在菜单项上显示UAC盾牌


1
@KenG:不用谢,我很高兴这对你有用。起初我假设你已经有了清单并且只是在尝试执行和等待,但后来我意识到你的问题实际上并没有这样说,所以我回去添加了一些细节。这样,我认为我的答案将来也会对其他人有用。 - Cody Gray
1
如果要生成的应用程序带有UAC清单,则可以使用“CreateProcess()”,而不需要使用“ShellExecutEx()”。如果应用程序的清单指示执行必要的UAC提示和提升,则“CreateProcess()”将执行此操作。 - Remy Lebeau
2
@MichalHosala:不,你是对的。根据《Vista UAC:权威指南》(http://www.codeproject.com/Articles/19165/Vista-UAC-The-Definitive-Guide):“对于需要通过其清单文件进行提升的进程,在Vista中,CreateProcess()会失败。应该注意的是,清单文件并没有真正地说“您必须使用管理令牌启动此进程”。相反,它说,“您不能使用权限少于这些权限的令牌来启动此进程”。因此,从CreateProcess()返回的错误消息ERROR_ELEVATION_REQUIRED(740)有些误导性。” - Remy Lebeau
3
如果清单文件中写明了 "requireAdministrator",但生成的进程没有提权,那么调用 CreateProcess() 将会因为 ERROR_ELEVATION_REQUIRED 而失败。 - Remy Lebeau
2
根据其他评论,应明确指出,如果清单中有适当的要求,您不需要在SHELLEXECUTEINFO结构的lpVerb成员中使用runas,但如果调用进程尚未提升,则需要使用ShellExecuteEx而不是CreateProcess。您可以从其他评论中推断出这一点,但尚未明确说明。 - BlueMonkMN
显示剩余3条评论

0
你可以创建一个管道来连接子进程的“stdin”(就像你要将输入重定向到该进程),然后等待管道断开。
请注意,该进程实际上不会接收重定向的输入。
例如,如果您尝试从非提升的命令提示符进行操作。
echo help | diskpart

你会看到高程,命令窗口会等待直到你关闭单独的窗口。


0

要以提升的特权运行,需要启动此过程的进程具有提升的特权。这通常需要一个安全服务或已经运行的东西来启动您的进程。这是从Vista开始的变化。它与拥有权限(通过您的ACL令牌)能够启动进程并使用从启动进程继承的适当特权级别有关。微软一直在努力推动人们创建一个处理所有提升功能需求并将其余部分留在最低特权用户空间的提升进程。自Vista Alpha以来一直在做这件事。这很麻烦,但出于安全原因,这是微软希望您采取的方式。

顺便说一句,如果您从安全位置(如Program Files目录等)启动应用程序,则ShellExec调用将无法工作。多年前不得不转向服务模型之前尝试过。

因此,要启动您的进程并绕过UAC,唯一的方法是从已经具有安全特权以继承的进程中启动。


如果调用应用程序没有提升权限,使用指定为“runas”的ShellExecute/Ex()函数可以从Program Files文件夹内启动提升的进程。我在我的一个项目中使用它,效果很好。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接