Create non-elevated process through Shell object

We usually allow users to start a final application after installation, it works in most time. However, due to installers have elevated/admin right, it may bring trouble if our final applications expect non-elevated right.

https://blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643 This article demostrated a way on creating process by using shell object, the new created process is a child process of Explorer.exe.

    CCoInitialize init;
    ShShellExecute(NULL,_T(“notepad.exe”),NULL,NULL,SW_NORMAL);

Here is the process trees:

image

We can see clearly notepad.exe is a child process of Explorer.exe.

Besides, it’s non-elevated.(This screen snapshot was taken from a VM because I disabled UAC in development environment)

image

The original code was written in ATL, let’s tidy it and use native com code, get rid of ATL.

static HRESULT GetDesktopAutomationObject(REFIID riid, void **ppv)
{
    IShellViewPtr spsv;
    HRESULT hr = FindDesktopFolderView(IID_PPV_ARGS(&spsv));
    if (spsv == NULL)
        return hr;

    IDispatchPtr spdispView;
    hr = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
    if (spdispView == NULL)
    {
        return hr;
    }
    return spdispView->QueryInterface(riid, ppv);
}

HRESULT ShShellExecute(PCWSTR pszOperation,
                       PCWSTR pszFile,
                       PCWSTR pszParameters,
                       PCWSTR pszDirectory,
                       int nShowCmd)
{
    IDispatchPtr spdispShell;
    IShellFolderViewDualPtr spFolderView;
    HRESULT hr = GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
    if (spFolderView!=NULL)
    {
        // use shell folder object, tested on winxp/win7/win10
        // it will run under explorer.exe context
        hr = spFolderView->get_Application(&spdispShell);
        if (spdispShell == NULL)
            return hr;
    }
    else
    {
            return hr;
    }

    return IShellDispatch2Ptr(spdispShell)
        ->ShellExecute(_bstr_t(pszFile),
        _variant_t(pszParameters ? pszParameters : L””),
        _variant_t(pszDirectory ? pszDirectory : L””),
        _variant_t(pszOperation ? pszOperation : L””),
        _variant_t(nShowCmd));
}

It used a util function from https://blogs.msdn.microsoft.com/oldnewthing/20130318-00/?p=4933/. Howeve,  the code used CSIDL_DESKTOP which was introduced in Vista. Even though, we can add some code and let it work under older OS, say, WinXP.

static HRESULT FindDesktopFolderView(REFIID riid, void **ppv)
{
    IShellWindowsPtr spShellWindows;
    HRESULT hr = spShellWindows.CreateInstance(CLSID_ShellWindows);
    if (spShellWindows == NULL)
        return hr;

    // try with the new interface first
    long lhwnd;
    IDispatchPtr spdisp;
    hr = spShellWindows->FindWindowSW(&_variant_t(CSIDL_DESKTOP), &_variant_t(),SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
    if (spdisp != NULL)
    {
        IShellBrowserPtr spBrowser;
        hr = IServiceProviderPtr(spdisp)->QueryService(SID_STopLevelBrowser,IID_PPV_ARGS(&spBrowser));
        if (spBrowser != NULL)
        {
            IShellViewPtr spView;
            hr = spBrowser->QueryActiveShellView(&spView);
            if (spView != NULL)
            {
                return spView->QueryInterface(riid, ppv);
            }
        }
    }

    // in winxp, have to enumrate registered windows
    LONG win_count = 0;
    spShellWindows->get_Count(&win_count);
    for (LONG i=0;i<win_count;i++)
    {
        IDispatchPtr spdisp;
        hr = spShellWindows->Item(_variant_t(i),&spdisp);
        if (spdisp != NULL)
        {
            IShellBrowserPtr spBrowser;
            hr = IServiceProviderPtr(spdisp)->QueryService(SID_STopLevelBrowser,IID_PPV_ARGS(&spBrowser));
            if (spBrowser == NULL)
                continue;

            IShellViewPtr spView;
            hr = spBrowser->QueryActiveShellView(&spView);
            if (spView == NULL)
                continue;

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

That’s all, we can put the following declaration in a header file so that others can use it without caring the detail:

HRESULT ShShellExecute(PCWSTR pszOperation,
                       PCWSTR pszFile,
                       PCWSTR pszParameters,
                       PCWSTR pszDirectory,
                       int nShowCmd);

It’s similar with ShellExecute.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s