简体   繁体   English

如何在Windows 10上从提升的管理员上下文启动非提升的管理员进程?

[英]How to launch non-elevated administrator process from elevated administrator context on Windows 10?

Is there an easy way to create a normal administrator process (not elevated) from an elevated administrator process? 是否有一种简单的方法可以从提升的管理员进程创建正常的管理员进程(未提升)? I am using Windows 10 pro. 我正在使用Windows 10专业版。 The situation is that I are trying to make some kind of deploy tools. 情况是我正在尝试制作某种部署工具。 The tool will be running with elevated administrator context in order to write files into "Program Files" ( and access other privileged resources). 该工具将使用提升的管理员上下文运行,以便将文件写入“Program Files”(并访问其他特权资源)。 But one of the step is to invoke an external program. 但其中一个步骤是调用外部程序。 That program seems to have strange issues when created with elevated administrator privilege. 使用提升的管理员权限创建时,该程序似乎存在奇怪的问题。 We have to launch it in a non-elevated administrator context. 我们必须在非提升的管理员环境中启动它。 I tried the approach in an MSDN blog, https://blogs.msdn.microsoft.com/winsdk/2010/05/31/dealing-with-administrator-and-standard-users-context it does not work at all. 我在MSDN博客中尝试了这种方法, https://blogs.msdn.microsoft.com/winsdk/2010/05/31/dealing-with-administrator-and-standard-users-context它根本不起作用。

Raymond Chen addressed this exact question on his "Old New Thing" blog on MSDN: Raymond Chen在MSDN上的“Old New Thing”博客中提到了这个确切的问题:

How can I launch an unelevated process from my elevated process and vice versa? 如何从我的升级过程中启动一个非高效的过程,反之亦然?

Going the other way is trickier. 走另一条路比较棘手。 For one thing, it's really hard to munge your token to remove the elevation nature properly. 首先,很难让你的令牌正确地去除高程性质。 And for another thing, even if you could do it, it's not the right thing to do, because the unelevated user may be different from the elevated user. 而另一方面,即使你能做到这一点,也不是正确的事情,因为没有提升的用户可能与提升的用户不同。

... ...

The solution here is to go back to Explorer and ask Explorer to launch the program for you. 这里的解决方案是返回资源管理器并请求Explorer为您启动该程序。 Since Explorer is running as the original unelevated user, the program (in this case, the Web browser) will run as Bob. 由于Explorer作为原始的非高效用户运行,因此程序(在本例中为Web浏览器)将作为Bob运行。 This is also important in the case that the handler for the file you want to open runs as an in-process extension rather than as a separate process, for in that case, the attempt to unelevate would be pointless since no new process was created in the first place. 在您要打开的文件的处理程序作为进程内扩展而不是作为单独的进程运行的情况下,这也很重要,因为在这种情况下,由于没有创建新进程,因此无法进行的尝试将毫无意义。第一名。 (And if the handler for the file tries to communicate with an existing unelevated copy of itself, things may fail because of UIPI.) (如果文件的处理程序试图与其自身的现有非高速副本进行通信,则由于UIPI,事情可能会失败。)

Raymond uses IShellFolderViewDual and IShellDispatch2 to accomplish that 1 : Raymond使用IShellFolderViewDualIShellDispatch2来完成1

#define STRICT
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>

// FindDesktopFolderView incorporated by reference

void GetDesktopAutomationObject(REFIID riid, void **ppv)
{
 CComPtr<IShellView> spsv;
 FindDesktopFolderView(IID_PPV_ARGS(&spsv));
 CComPtr<IDispatch> spdispView;
 spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
 spdispView->QueryInterface(riid, ppv);
}

void ShellExecuteFromExplorer(
    PCWSTR pszFile,
    PCWSTR pszParameters = nullptr,
    PCWSTR pszDirectory  = nullptr,
    PCWSTR pszOperation  = nullptr,
    int nShowCmd         = SW_SHOWNORMAL)
{
 CComPtr<IShellFolderViewDual> spFolderView;
 GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
 CComPtr<IDispatch> spdispShell;
 spFolderView->get_Application(&spdispShell);

 CComQIPtr<IShellDispatch2>(spdispShell)
    ->ShellExecute(CComBSTR(pszFile),
                   CComVariant(pszParameters ? pszParameters : L""),
                   CComVariant(pszDirectory ? pszDirectory : L""),
                   CComVariant(pszOperation ? pszOperation : L""),
                   CComVariant(nShowCmd));
}

int __cdecl wmain(int argc, wchar_t **argv)
{
 if (argc < 2) return 0;

 CCoInitialize init;
 ShellExecuteFromExplorer(
    argv[1],
    argc >= 3 ? argv[2] : L"",
    argc >= 4 ? argv[3] : L"",
    argc >= 5 ? argv[4] : L"",
    argc >= 6 ? _wtoi(argv[5]) : SW_SHOWNORMAL);

 return 0;
}

Open an elevated command prompt, and then run this program in various ways. 打开提升的命令提示符,然后以各种方式运行此程序。

scratch http://www.msn.com/
Open an unelevated Web page in the user's default Web browser. 在用户的默认Web浏览器中打开未加载的Web页面。

scratch cmd.exe "" C:\\Users "" 3
Open an unelevated command prompt at C:\\Users, maximized. 在C:\\ Users打开一个未加载的命令提示符,最大化。

scratch C:\\Path\\To\\Image.bmp "" "" edit
Edit a bitmap in an unelevated image editor 在未高速图像编辑器中编辑位图

1 : the implementation of FindDesktopFolderView() is in another article on Raymond's blog: 1FindDesktopFolderView()的实现在Raymond博客的另一篇文章中:

Manipulating the positions of desktop icons : 操纵桌面图标的位置

void FindDesktopFolderView(REFIID riid, void **ppv)
{
 CComPtr<IShellWindows> spShellWindows;
 spShellWindows.CoCreateInstance(CLSID_ShellWindows);

 CComVariant vtLoc(CSIDL_DESKTOP);
 CComVariant vtEmpty;
 long lhwnd;
 CComPtr<IDispatch> spdisp;
 spShellWindows->FindWindowSW(
     &vtLoc, &vtEmpty,
     SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);

 CComPtr<IShellBrowser> spBrowser;
 CComQIPtr<IServiceProvider>(spdisp)->
     QueryService(SID_STopLevelBrowser,
                  IID_PPV_ARGS(&spBrowser));

 CComPtr<IShellView> spView;
 spBrowser->QueryActiveShellView(&spView);

 spView->QueryInterface(riid, ppv);
}

Here is my version of Raymond Chen's code that adds error handling via C++ exceptions. 这是我的Raymond Chen代码版本,它通过C ++异常添加了错误处理。

First we declare some helper functions for throwing std::system_error exception from HRESULT , converting a GUID to string and a RAII wrapper for COM initialization. 首先,我们声明一些辅助函数,用于从HRESULT抛出std::system_error异常,将GUID转换为字符串,并将RAII包装器转换为COM初始化。

#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>
#include <iostream>
#include <system_error>

template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
    if( FAILED( hr ) )
        throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}

template< typename ResultT = std::string >
ResultT to_string( REFIID riid )
{
    LPOLESTR pstr = nullptr;
    if( SUCCEEDED( ::StringFromCLSID( riid, &pstr ) ) ) 
    {
        // Use iterator arguments to cast from wchar_t to char if element type of ResultT is char.
        ResultT result{ pstr, pstr + wcslen( pstr ) };
        ::CoTaskMemFree( pstr ); pstr = nullptr;
        return result;
    }
    return {};
}

struct ComInit
{
    ComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "Could not initialize COM" ); }
    ~ComInit() { ::CoUninitialize(); }
    ComInit( ComInit const& ) = delete;
    ComInit& operator=( ComInit const& ) = delete;
};

This is followed by the functions that do the actual work. 接下来是执行实际工作的函数。 This is basically the same code as in Remy Lebeau's answer , but with added error handling (and my personal formatting style). 这基本上与Remy Lebeau的答案中的代码相同,但增加了错误处理(以及我的个人格式化风格)。

void FindDesktopFolderView( REFIID riid, void **ppv )
{
    CComPtr<IShellWindows> spShellWindows;
    ThrowIfFailed( 
        spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
        "Could not create instance of IShellWindows" );

    CComVariant vtLoc{ CSIDL_DESKTOP };
    CComVariant vtEmpty;
    long lhwnd = 0;
    CComPtr<IDispatch> spdisp;
    ThrowIfFailed( 
        spShellWindows->FindWindowSW(
            &vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
        "Could not find desktop shell window" );

    CComQIPtr<IServiceProvider> spProv{ spdisp };
    if( ! spProv )
        ThrowIfFailed( E_NOINTERFACE, "Could not query interface IServiceProvider" );

    CComPtr<IShellBrowser> spBrowser;
    ThrowIfFailed( 
        spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser) ),
        "Could not query service IShellBrowser" );

    CComPtr<IShellView> spView;
    ThrowIfFailed( 
        spBrowser->QueryActiveShellView( &spView ),
        "Could not query active IShellView" );

    ThrowIfFailed( 
        spView->QueryInterface( riid, ppv ),
        "Could not query interface " + to_string( riid ) + " from IShellView" );
}

void GetDesktopAutomationObject( REFIID riid, void **ppv )
{
    CComPtr<IShellView> spsv;
    FindDesktopFolderView( IID_PPV_ARGS(&spsv) );

    CComPtr<IDispatch> spdispView;
    ThrowIfFailed( 
        spsv->GetItemObject( SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView) ),
        "Could not get item object SVGIO_BACKGROUND from IShellView" );
    ThrowIfFailed( 
        spdispView->QueryInterface( riid, ppv ),
        "Could not query interface " + to_string( riid ) + " from ShellFolderView" );
}

void ShellExecuteFromExplorer(
    PCWSTR pszFile,
    PCWSTR pszParameters = nullptr,
    PCWSTR pszDirectory  = nullptr,
    PCWSTR pszOperation  = nullptr,
    int nShowCmd         = SW_SHOWNORMAL)
{
    CComPtr<IShellFolderViewDual> spFolderView;
    GetDesktopAutomationObject( IID_PPV_ARGS(&spFolderView) );

    CComPtr<IDispatch> spdispShell;
    ThrowIfFailed( 
        spFolderView->get_Application( &spdispShell ),
        "Could not get application object from IShellFolderViewDual" );

    CComQIPtr<IShellDispatch2> spdispShell2{ spdispShell };
    if( !spdispShell2 )
        ThrowIfFailed( E_NOINTERFACE, "Could not query interface IShellDispatch2" );

    ThrowIfFailed( 
        spdispShell2->ShellExecute(
            CComBSTR{ pszFile },
            CComVariant{ pszParameters ? pszParameters : L"" },
            CComVariant{ pszDirectory ? pszDirectory : L"" },
            CComVariant{ pszOperation ? pszOperation : L"" },
            CComVariant{ nShowCmd } ),
        "ShellExecute failed" );
}

Usage example: 用法示例:

int main()
{
    try
    {
        ComInit init;
        ShellExecuteFromExplorer( L"http://www.stackoverflow.com" );
    }
    catch( std::system_error& e )
    {
        std::cout << "ERROR: " << e.what() << "\n"
            << "Error code: " << e.code() << std::endl;
    }
}

Additional notes: 补充说明:

When using this method, you may notice that the window of the launched application does not always come to the foreground, especially if it is already running. 使用此方法时,您可能会注意到已启动的应用程序的窗口并不总是出现在前台,特别是如果它已在运行。 My workaround is to call AllowSetForegroundWindow( ASFW_ANY ) before the call to ShellExecuteFromExplorer() to enable the process to bring itself to the foreground (we can't specify a process ID because we don't know the process that will be launched beforehand). 我的解决方法是在调用ShellExecuteFromExplorer() AllowSetForegroundWindow( ASFW_ANY )之前调用AllowSetForegroundWindow( ASFW_ANY )以使进程能够将自己置于前台(我们无法指定进程ID,因为我们不知道将事先启动的进程)。

The solution is a bit complex. 解决方案有点复杂。 Maybe you can not go from elevated administrator to non-elevated administrator directly by getting a kind of access token and passing it to createprocesswithtoken, But you can go one step further in another direction. 也许你不能通过获取一种访问令牌并将其传递给createprocesswithtoken直接从提升管理员到非提升管理员,但你可以在另一个方向上更进一步。 You can go from elevated administrator to system account, which has even higher privilege. 您可以从提升的管理员转到具有更高权限的系统帐户。 From system account privilege, you should be able to launch a process in a non-elevated administrator context. 从系统帐户权限,您应该能够在非提升的管理员上下文中启动进程。 Search using keyword "impersonate" can give you a lot of examples. 使用关键字“impersonate”进行搜索可以为您提供大量示例。 Then how to go from elevated administrator to system? 那么如何从高架管理员转向系统? You can only write a system service and create/start the service under elevated administrator context. 您只能编写系统服务并在提升的管理员上下文下创建/启动服务。

Here's the Raymond Chen method, with error handling, and no wretched exceptions. 这是Raymond Chen方法,有错误处理,没有可怕的异常。

#include <atlbase.h>
#include <Shlobj.h>

HRESULT FindDesktopFolderView(REFIID riid, void **ppv)
{
HRESULT hr;
CComPtr<IShellWindows> spShellWindows;
hr = spShellWindows.CoCreateInstance(CLSID_ShellWindows);
if(FAILED(hr))
    return hr;

CComVariant vtLoc { 0 };    // 0 = CSIDL_DESKTOP
CComVariant vtEmpty;
long lhwnd = 0;
CComPtr<IDispatch> spdisp;
hr = spShellWindows->FindWindowSW(&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
if(FAILED(hr))
    return hr;

CComQIPtr<IServiceProvider> spProv{ spdisp };
if(!spProv)
    return E_NOINTERFACE;

CComPtr<IShellBrowser> spBrowser;
hr = spProv->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser));
if(FAILED(hr))
    return hr;

CComPtr<IShellView> spView;
hr = spBrowser->QueryActiveShellView(&spView);
if(FAILED(hr))
    return hr;

return spView->QueryInterface(riid, ppv);
}


HRESULT GetDesktopAutomationObject(REFIID riid, void **ppv)
{
HRESULT hr;
CComPtr<IShellView> spsv;
hr = FindDesktopFolderView(IID_PPV_ARGS(&spsv));
if(FAILED(hr))
    return hr;
if(!spsv)
    return E_NOINTERFACE;

CComPtr<IDispatch> spdispView;
hr = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
if(FAILED(hr))
    return hr;

return spdispView->QueryInterface(riid, ppv);
}


HRESULT ShellExecuteFromExplorer(PCWSTR pszFile, PCWSTR pszParameters, PCWSTR pszDirectory, PCWSTR pszOperation, int nShowCmd)
{
HRESULT hr;
CComPtr<IShellFolderViewDual> spFolderView;
hr = GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
if(FAILED(hr))
    return hr;

CComPtr<IDispatch> spdispShell;
hr = spFolderView->get_Application(&spdispShell);
if(FAILED(hr))
    return hr;

CComQIPtr<IShellDispatch2> spdispShell2{ spdispShell };
if(!spdispShell2)
    return E_NOINTERFACE;

// without this, the launched app is not moved to the foreground
AllowSetForegroundWindow(ASFW_ANY);

return spdispShell2->ShellExecute(
        CComBSTR{ pszFile },
        CComVariant{ pszParameters ? pszParameters : L"" },
        CComVariant{ pszDirectory ? pszDirectory : L"" },
        CComVariant{ pszOperation ? pszOperation : L"" },
        CComVariant{ nShowCmd } );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从非提升进程非默认访问提升进程 - non-default access to an elevated process from a non-elevated process 非提升进程有没有办法将文件复制到受保护的目录? - Is there a way an NON-elevated process can copy a file to a protected directory? 在Windows上检查非提升权限用户的文件权限 - Checking File Permission on Windows for non elevated users 非提升时,GetTokenInformation 不会返回所有权限的状态 - GetTokenInformation doesn't return statue of all privileges when non-elevated 如何在Windows上通过QProcess启动提升的子流程? - How to start an elevated subprocess through QProcess on Windows? 如何使用本机C ++检查进程是否在Windows 7中具有提升权限? - How to check if a process has elevated privileges in windows 7 using native C++? 使用CreateProcessAsUser函数从提升的流程创建非提升的流程 - Create nonelevated process from elevated process with CreateProcessAsUser function 如何在Windows 7中请求管理员权限? - How to ask for Administrator privileges in Windows 7? 请求在 Windows AS 普通用户中以管理员身份启动进程 - Requesting to start process as administrator in Windows AS regular user 如何从提升程序中启动提升控制台(cmd.exe),就像手动完成一样? - How to start an elevated console (cmd.exe) from an elevated program just as if that was done manually?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM