Files
nsis-plugin-nsshellexecasuser/src/ShellExecAsUser.cpp
T
Simone c98a0d37d9 chore: initial commit (extracted from Launchers monorepo)
Plugin: nsShellExecAsUser v1.0.0
Architectures: x86-ansi, x86-unicode, amd64-unicode
License: zlib
2026-04-29 14:04:59 +02:00

273 lines
7.6 KiB
C++

// ShellExecAsUser.cpp : Defines the entry point for the DLL application.
//
#include <windows.h>
#include <shlDisp.h>
#include <atlbase.h>
#include <Exdisp.h>
#include <shobjidl.h>
#include <shlguid.h>
//#include <process.h>
#include "pluginapi.h" // nsis plugin
#include <shellApi.h>
//#pragma comment(linker,"/merge:.rdata=.text")
//VistaTools
#define NO_DLL_IMPORTS 1
#define DONTWANT_RunNonElevated 1
#define IMPLEMENT_VISTA_TOOLS
#include "VistaTools.cxx"
//////////////////////////////////////
//missing in old sdk, had to replace shlwapi.lib as well
//LWSTDAPI IUnknown_QueryService(__in IUnknown* punk, __in REFGUID guidService, __in REFIID riid, __deref_out void ** ppvOut);
HINSTANCE g_hInstance;
HWND g_hwndParent;
HWND g_hwnd;
HANDLE g_hThread;
typedef struct _ExecShellParams {
TCHAR action[50];
TCHAR command[_MAX_PATH];
TCHAR params[1024];
TCHAR directory[_MAX_PATH];
int iShow;
} ExecShellParams;
UINT_PTR NSISPluginCallback(enum NSPIM Event)
{
switch(Event)
{
case NSPIM_UNLOAD:
if (g_hThread) {
//OutputDebugString("ExecShellAsUser: NSPIM_UNLOAD wait...");
::PostMessage(g_hwnd, WM_USER+11, 0, 0);
::WaitForSingleObject(g_hThread, INFINITE);
::CloseHandle(g_hThread);
//OutputDebugString("ExecShellAsUser: NSPIM_UNLOAD");
}
break;
}
return NULL;
}
//http://brandonlive.com/2008/04/27/getting-the-shell-to-run-an-application-for-you-part-2-how/
static CComQIPtr<IShellDispatch2> retrieveDesktop()
{
CComPtr<IShellWindows> psw;
HRESULT hr = psw.CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER);
if (SUCCEEDED(hr))
{
HWND hwnd;
CComPtr<IDispatch> pdisp;
VARIANT vEmpty;
vEmpty.vt = VT_EMPTY;
if (S_OK == (hr = psw->FindWindowSW(&vEmpty, &vEmpty, 0x8/*SWC_DESKTOP*/, (long*)&hwnd, SWFO_NEEDDISPATCH, &pdisp)))
{
CComPtr<IShellBrowser> psb;
hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, __uuidof(IShellBrowser), (void**) &psb); //IID_PPV_ARGS(&psb)
if (SUCCEEDED(hr))
{
CComPtr<IShellView> psv;
hr = psb->QueryActiveShellView(&psv);
CComPtr<IDispatch> pdispBackground;
HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, __uuidof(IDispatch), (void**) &pdispBackground);
if (SUCCEEDED(hr))
{
if (CComQIPtr<IShellFolderViewDual> psfvd = pdispBackground)
{
CComPtr<IDispatch> pdisp;
hr = psfvd->get_Application(&pdisp);
if (SUCCEEDED(hr))
{
return CComQIPtr<IShellDispatch2>(pdisp);
}
}
}
}
} else {
TCHAR buf[100];
wsprintf(buf, _T("ShellExecAsUser: FindWindowSW failed: %x"), hr);
OutputDebugString(buf);
}
}
return CComQIPtr<IShellDispatch2>();
}
static int getShowMode(const TCHAR* s)
{
if (s[0] == 0) return SW_SHOWNORMAL;
const TCHAR* strModes[] = { _T("SW_SHOWDEFAULT"), _T("SW_SHOWNORMAL"), _T("SW_SHOWMAXIMIZED"), _T("SW_SHOWMINIMIZED"), _T("SW_HIDE") };
int modes[] = { SW_SHOWDEFAULT, SW_SHOWNORMAL, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED, SW_HIDE };
for (int i=0; i<sizeof(strModes)/sizeof(TCHAR*); ++i) {
if (lstrcmp(s, strModes[i]) == 0)
return modes[i];
}
return SW_SHOWNORMAL;
}
static bool runCommand(IShellDispatch2* psd, ExecShellParams* params)
{
if (!psd) return false;
CComBSTR command(params->command);
CComVariant vAction, vParams, vDir;
if (params->action[0] != 0) vAction = params->action;
if (params->params[0] != 0) vParams = params->params;
if (params->directory[0] != 0) vDir = params->directory;
CComVariant vShow;
if (params->iShow != -1) vShow = params->iShow;
//#pragma push_macro("ShellExecute")
//#undef ShellExecute
HRESULT hr = psd->ShellExecute(command, vParams, vDir, vAction, vShow);
//#pragma pop_macro("ShellExecute")
return hr == S_OK;
}
static LRESULT WndProc(HWND hWnd, UINT uMsg,WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CREATE:
{
void* param = ((LPCREATESTRUCT)lParam)->lpCreateParams;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)param);
break;
}
case WM_USER+10:
{
ExecShellParams* p = (ExecShellParams*)lParam;
IShellDispatch2* pSD = (IShellDispatch2*)::GetWindowLongPtr(hWnd, GWLP_USERDATA);
runCommand(pSD, p);
delete p;
break;
}
case WM_USER+11:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
return ::DefWindowProc(hWnd,uMsg,wParam,lParam);
};
return 0;
}
HWND CreateMessageWindow(void* data)
{
WNDCLASSEX wincl;
ZeroMemory(&wincl, sizeof(WNDCLASSEX));
wincl.hInstance = g_hInstance;
wincl.lpszClassName = _T("NSIS_DESKTOP_LAUNCH_CLASS");
wincl.lpfnWndProc = (WNDPROC)WndProc;
wincl.cbSize = sizeof (WNDCLASSEX);
ATOM atom = RegisterClassEx (&wincl);
return CreateWindowEx(
0,
MAKEINTATOM(atom),
_T(""),
0,
0,
0,
0,
0,
HWND_MESSAGE,
NULL,
wincl.hInstance,
data);
}
static unsigned __stdcall WorkerThreadProc(void* pParam)
{
HANDLE hRunEvent = (HANDLE) pParam;
::CoInitialize(NULL);
{
CComPtr<IShellDispatch2> pSD = retrieveDesktop();
OutputDebugString(pSD.p ? _T("ExecShellAsUser: got desktop") : _T("ExecShellAsUser: failed to retrieve desktop!"));
if (pSD.p) g_hwnd = CreateMessageWindow(pSD.p);
SetEvent(hRunEvent);
if (g_hwnd) {
MSG msg;
while ( GetMessage(&msg, NULL, 0, 0) > 0 )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
::CoUninitialize();
OutputDebugString(_T("ExecShellAsUser: thread finished"));
return 0;
}
#define EMPTY_TO_NULL(s) (s[0] == 0 ? NULL : s)
extern "C"
void __declspec(dllexport) ShellExecAsUser(HWND hwndParent, int string_size,
TCHAR *variables, stack_t **stacktop,
extra_parameters *extra)
{
static int initDone=0;
g_hwndParent=hwndParent;
EXDLL_INIT();
if (initDone == 0) {
initDone=1;
extra->RegisterPluginCallback(g_hInstance, NSISPluginCallback);
if (IsVista() && IsElevated(NULL) == S_OK) {
//note: COM calls to shell interfaces have to be executed on another thread,
//otherwise I got RPC_E_CANTCALLOUT_ININPUTSYNCCALL error.
HANDLE hRunEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); //manual reset
g_hThread = (HANDLE)_beginthreadex( NULL, 0, WorkerThreadProc, hRunEvent, 0, NULL);
if (g_hThread) ::WaitForSingleObject(hRunEvent, INFINITE);
CloseHandle(hRunEvent);
OutputDebugString(_T("ExecShellAsUser: elevated process detected"));
} else {
OutputDebugString(_T("ExecShellAsUser: process is not elevated, will fallback to ShellExecute"));
}
}
// action command [parameters] [SW_SHOWDEFAULT | SW_SHOWNORMAL | SW_SHOWMAXIMIZED | SW_SHOWMINIMIZED | SW_HIDE]
ExecShellParams execShellParams;
ExecShellParams* params = g_hwnd ? new ExecShellParams : &execShellParams;
TCHAR show[50];
popstring(params->action);
popstring(params->command);
popstring(params->params);
popstring(show);
params->iShow = getShowMode(show);
TCHAR* dir = getuservariable(INST_OUTDIR);
if (g_hwnd) {
if (dir) lstrcpyn(params->directory, dir, sizeof(params->directory)/sizeof(params->directory[0]));
::PostMessage(g_hwnd, WM_USER+10, 0, (LPARAM)params);
} else {
::ShellExecute(NULL, EMPTY_TO_NULL(params->action), params->command, EMPTY_TO_NULL(params->params), dir, params->iShow);
}
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
if(ul_reason_for_call==DLL_PROCESS_ATTACH)
{
g_hInstance = (HINSTANCE) hModule;
} else if (ul_reason_for_call==DLL_PROCESS_DETACH) {
OutputDebugString(_T("ExecShellAsUser: DLL_PROCESS_DETACH"));
}
return TRUE;
}