ExecInExplorer.cpp (6513B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // This file is an NSIS plugin which exports a function that starts a process 6 // from a provided path by using the shell automation API to have explorer.exe 7 // invoke ShellExecute. This roundabout method of starting a process is useful 8 // because it means the new process will use the integrity level and security 9 // token of the shell, so it allows starting an unelevated process from inside 10 // an elevated one. The method is based on 11 // https://blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643 12 // but the code has been rewritten to remove the need for ATL or the C runtime. 13 14 // Normally an NSIS installer would use the UAC plugin, which itself uses both 15 // an unelevated and an elevated process, and the elevated process can invoke 16 // functions in the unelevated one, so this plugin wouldn't be needed. 17 // But uninstallers are often directly run elevated because that's just how 18 // the Windows UI launches them, so there is no unelevated process. This 19 // plugin allows starting a needed unelevated process in that situation. 20 21 #include <windows.h> 22 #include <shlobj.h> 23 24 #pragma comment(lib, "shlwapi.lib") 25 26 static IShellView* 27 GetDesktopWindowShellView() 28 { 29 IShellView* view = nullptr; 30 IShellWindows* shell = nullptr; 31 CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, 32 IID_PPV_ARGS(&shell)); 33 if (shell) { 34 VARIANT empty; 35 VariantInit(&empty); 36 37 VARIANT loc; 38 loc.vt = VT_VARIANT | VT_BYREF; 39 PIDLIST_ABSOLUTE locList; 40 SHGetFolderLocation(nullptr, CSIDL_DESKTOP, nullptr, 0, &locList); 41 loc.byref = locList; 42 43 HWND windowHandle = 0; 44 IDispatch* dispatch = nullptr; 45 46 shell->FindWindowSW(&loc, &empty, SWC_DESKTOP, (long*)&windowHandle, 47 SWFO_NEEDDISPATCH, &dispatch); 48 if (dispatch) { 49 IServiceProvider* provider = nullptr; 50 dispatch->QueryInterface(IID_PPV_ARGS(&provider)); 51 if (provider) { 52 IShellBrowser* browser = nullptr; 53 provider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&browser)); 54 if (browser) { 55 browser->QueryActiveShellView(&view); 56 browser->Release(); 57 } 58 provider->Release(); 59 } 60 dispatch->Release(); 61 } 62 shell->Release(); 63 } 64 65 return view; 66 } 67 68 static IShellDispatch2* 69 GetApplicationFromShellView(IShellView* view) 70 { 71 IShellDispatch2* shellDispatch = nullptr; 72 IDispatch* viewDisp = nullptr; 73 HRESULT hr = view->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&viewDisp)); 74 if (SUCCEEDED(hr)) { 75 IShellFolderViewDual* shellViewFolder = nullptr; 76 viewDisp->QueryInterface(IID_PPV_ARGS(&shellViewFolder)); 77 if (shellViewFolder) { 78 IDispatch* dispatch = nullptr; 79 shellViewFolder->get_Application(&dispatch); 80 if (dispatch) { 81 dispatch->QueryInterface(IID_PPV_ARGS(&shellDispatch)); 82 dispatch->Release(); 83 } 84 shellViewFolder->Release(); 85 } 86 viewDisp->Release(); 87 } 88 return shellDispatch; 89 } 90 91 static bool 92 ShellExecInExplorerProcess(wchar_t* path, wchar_t* args = nullptr) 93 { 94 bool rv = false; 95 if (SUCCEEDED(CoInitialize(nullptr))) { 96 IShellView *desktopView = GetDesktopWindowShellView(); 97 if (desktopView) { 98 IShellDispatch2 *shellDispatch = GetApplicationFromShellView(desktopView); 99 if (shellDispatch) { 100 BSTR bstrPath = SysAllocString(path); 101 VARIANT vArgs; 102 VariantInit(&vArgs); 103 if (args) { 104 vArgs.vt = VT_BSTR; 105 vArgs.bstrVal = SysAllocString(args); 106 } 107 rv = SUCCEEDED(shellDispatch->ShellExecuteW(bstrPath, vArgs, VARIANT{}, 108 VARIANT{}, VARIANT{})); 109 VariantClear(&vArgs); 110 SysFreeString(bstrPath); 111 shellDispatch->Release(); 112 } 113 desktopView->Release(); 114 } 115 CoUninitialize(); 116 } 117 return rv; 118 } 119 120 struct stack_t { 121 stack_t* next; 122 TCHAR text[MAX_PATH]; 123 }; 124 125 /** 126 * Removes an element from the top of the NSIS stack 127 * 128 * @param stacktop A pointer to the top of the stack 129 * @param str The string to pop to 130 * @param len The max length 131 * @return 0 on success 132 */ 133 int 134 popstring(stack_t **stacktop, TCHAR *str, int len) 135 { 136 // Removes the element from the top of the stack and puts it in the buffer 137 stack_t *th; 138 if (!stacktop || !*stacktop) { 139 return 1; 140 } 141 142 th = (*stacktop); 143 lstrcpyn(str, th->text, len); 144 *stacktop = th->next; 145 HeapFree(GetProcessHeap(), 0, th); 146 return 0; 147 } 148 149 /** 150 * Adds an element to the top of the NSIS stack 151 * 152 * @param stacktop A pointer to the top of the stack 153 * @param str The string to push on the stack 154 * @param len The length of the string to push on the stack 155 * @return 0 on success 156 */ 157 void 158 pushstring(stack_t **stacktop, const TCHAR *str, int len) 159 { 160 stack_t *th; 161 if (!stacktop) { 162 return; 163 } 164 th = (stack_t*)HeapAlloc(GetProcessHeap(), 0, sizeof(stack_t) + len); 165 lstrcpyn(th->text, str, len); 166 th->next = *stacktop; 167 *stacktop = th; 168 } 169 170 /** 171 * Starts an executable or URL from the shell process. 172 * 173 * @param stacktop Pointer to the top of the stack, AKA the first parameter to 174 the plugin call. Should contain the file or URL to execute. 175 * @return 1 if the file/URL was executed successfully, 0 if it was not 176 */ 177 extern "C" void __declspec(dllexport) 178 Exec(HWND, int, TCHAR *, stack_t **stacktop, void *) 179 { 180 wchar_t path[MAX_PATH + 1]; 181 wchar_t args[MAX_PATH + 1]; 182 bool rv = false; 183 bool restoreArgString = false; 184 // We're skipping building the C runtime to keep the file size low, so we 185 // can't use a normal string initialization because that would call memset. 186 path[0] = L'\0'; 187 args[0] = L'\0'; 188 popstring(stacktop, path, MAX_PATH); 189 if (popstring(stacktop, args, MAX_PATH) == 0) { 190 // This stack item may not be for us, but we don't know yet. 191 restoreArgString = true; 192 } 193 194 if (lstrcmpW(args, L"/cmdargs") == 0) { 195 popstring(stacktop, args, MAX_PATH); 196 rv = ShellExecInExplorerProcess(path, args); 197 } else { 198 // If the stack wasn't empty, then we popped something that wasn't for us. 199 if (restoreArgString) { 200 pushstring(stacktop, args, lstrlenW(args)); 201 } 202 rv = ShellExecInExplorerProcess(path); 203 } 204 205 pushstring(stacktop, rv ? L"1" : L"0", 2); 206 } 207 208 BOOL APIENTRY 209 DllMain(HMODULE, DWORD, LPVOID) 210 { 211 return TRUE; 212 }