Try to de-elevate after applying an update to not run UI as admin

This commit is contained in:
baldurk
2025-07-31 16:15:34 +01:00
parent e6a7b36cdc
commit 9bfcb76e0b
2 changed files with 122 additions and 4 deletions
+15
View File
@@ -361,6 +361,10 @@ int main(int argc, char *argv[])
hideOption(updateFailed);
parser.addOption(updateFailed);
QCommandLineOption updateDoneAdmin(lit("updatedone_admin"));
hideOption(updateDoneAdmin);
parser.addOption(updateDoneAdmin);
QCommandLineOption updateDone(lit("updatedone"));
hideOption(updateDone);
parser.addOption(updateDone);
@@ -413,11 +417,22 @@ int main(int argc, char *argv[])
if(parser.isSet(updateDone))
{
qInfo() << "Finishing update as user";
updateApplied = true;
// the renderdoccmd updater that runs us is from the old version, so older versions might be
// running us as admin expecting the version number to be updated.
// if we're not running as admin, this will immediately exit
RENDERDOC_UpdateInstalledVersionNumber();
}
if(parser.isSet(updateDoneAdmin))
{
qInfo() << "Finishing update as admin";
RENDERDOC_UpdateInstalledVersionNumber();
return 0;
}
QString remoteHost;
uint remoteIdent = 0;
+107 -4
View File
@@ -33,6 +33,8 @@
#include "resource.h"
#include <Psapi.h>
#include <shldisp.h>
#include <shlobj.h>
#include <tlhelp32.h>
static std::string conv(const std::wstring &str)
@@ -243,16 +245,22 @@ struct UpgradeCommand : public Command
{
private:
std::wstring wide_path;
bool dryrun = false;
public:
UpgradeCommand() {}
virtual void AddOptions(cmdline::parser &parser) { parser.add<std::string>("path", 0, ""); }
virtual void AddOptions(cmdline::parser &parser)
{
parser.add<std::string>("path", 0, "");
parser.add("dryrun", '\0', "");
}
virtual const char *Description() { return "Internal use only!"; }
virtual bool IsInternalOnly() { return true; }
virtual bool IsCaptureCommand() { return false; }
virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &)
{
wide_path = conv(parser.get<std::string>("path"));
dryrun = parser.exist("dryrun");
return true;
}
@@ -261,6 +269,8 @@ public:
if(wide_path.back() != '\\' && wide_path.back() != '/')
wide_path += L'\\';
CoInitialize(NULL);
// Wait for UI to exit
Sleep(3000);
@@ -272,7 +282,11 @@ public:
mz_bool b = mz_zip_reader_init_file(&zip, "./update.zip", 0);
if(b)
if(dryrun)
{
successful = true;
}
else if(b)
{
mz_uint numfiles = mz_zip_reader_get_num_files(&zip);
@@ -417,12 +431,12 @@ public:
failReason = L"\"Failed to open update .zip file - possibly corrupted.\"";
}
// run original UI exe and tell it an update succeeded
// run original UI exe (as admin still) and tell it an update succeeded so that it can do any last updates
std::wstring cmdline = L"\"";
cmdline += wide_path;
cmdline += L"/qrenderdoc.exe\" ";
if(successful)
cmdline += L"--updatedone";
cmdline += L"--updatedone_admin";
else
cmdline += L"--updatefailed " + failReason;
@@ -445,6 +459,95 @@ public:
CloseHandle(pi.hThread);
}
// try to launch the UI as a regular user again
// from https://devblogs.microsoft.com/oldnewthing/?p=2643
// Old New Thing "How can I launch an unelevated process from my elevated process and vice
// versa?" November 18th, 2013 for when that blog link inevitably breaks
// we deliberately don't try to release any of these objects - they will just leak as we're
// going to exit in a moment either way
IShellWindows *shellWindows = NULL;
if(SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL, IID_IShellWindows,
(void **)&shellWindows)))
{
// documentation for FindWindowSW says location must be VT_VARIANT | VT_BYREF but this is what
// the examples do and it works
VARIANT location = {};
location.vt = VT_I4;
location.lVal = CSIDL_DESKTOP;
VARIANT empty = {};
long hwnd = 0;
IDispatch *spdisp = NULL;
if(SUCCEEDED(shellWindows->FindWindowSW(&location, &empty, SWC_DESKTOP, &hwnd,
SWFO_NEEDDISPATCH, &spdisp)))
{
// go through the chain of interfaces to get explorer's dispatch helper
IServiceProvider *querier = NULL;
IShellBrowser *spBrowser = NULL;
IShellView *spView = NULL;
IShellView *spsv = NULL;
IDispatch *spdispView = NULL;
IShellFolderViewDual *spFolderView = NULL;
IDispatch *spdispShell = NULL;
IShellDispatch2 *dispatcher = NULL;
if(SUCCEEDED(spdisp->QueryInterface(__uuidof(IServiceProvider), (void **)&querier)) &&
SUCCEEDED(querier->QueryService(SID_STopLevelBrowser, __uuidof(IShellBrowser),
(void **)&spBrowser)) &&
SUCCEEDED(spBrowser->QueryActiveShellView(&spView)) &&
SUCCEEDED(spView->QueryInterface(__uuidof(IShellView), (void **)&spsv)) &&
SUCCEEDED(spsv->GetItemObject(SVGIO_BACKGROUND, __uuidof(IDispatch), (void **)&spdispView)) &&
SUCCEEDED(spdispView->QueryInterface(__uuidof(IShellFolderViewDual),
(void **)&spFolderView)) &&
SUCCEEDED(spFolderView->get_Application(&spdispShell)) &&
SUCCEEDED(spdispShell->QueryInterface(__uuidof(IShellDispatch2), (void **)&dispatcher)))
{
VARIANT show = {};
show.vt = VT_I4;
show.lVal = SW_SHOWNORMAL;
std::wstring qrenderdoc = wide_path + L"/qrenderdoc.exe";
BSTR path = SysAllocStringLen(qrenderdoc.c_str(), (UINT)qrenderdoc.size());
memcpy(path, qrenderdoc.c_str(), qrenderdoc.size());
VARIANT param = {};
param.vt = VT_BSTR;
param.bstrVal = SysAllocString(L"--updatedone");
// return value unclear, we just assume if we got this far that it works.
dispatcher->ShellExecute(path, param, empty, empty, show);
SysFreeString(param.bstrVal);
SysFreeString(path);
return 0;
}
}
}
// if the de-elevation failed, we will just launch as admin
cmdline = L"\"";
cmdline += wide_path;
cmdline += L"/qrenderdoc.exe\" --updatedone";
ZeroMemory(paramsAlloc, sizeof(wchar_t) * 512);
wcscpy_s(paramsAlloc, 511, cmdline.c_str());
ZeroMemory(&pi, sizeof(pi));
ZeroMemory(&si, sizeof(si));
CreateProcessW(NULL, paramsAlloc, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if(pi.dwProcessId != 0)
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
delete[] paramsAlloc;
return 0;