Search code examples
powershellwinapi

pass arguments with ShellExecute to Powershell with admin


I'm trying to run PowerShell in admin using ShellExecute within a Qt program, but the arguments don't seem to be transferring.

QString ruleName = QCoreApplication::applicationName();
QString exePath = QCoreApplication::applicationFilePath();

// Define the embedded PowerShell scripts as strings
const QString checkFirewallScript = QString(
"param([string]$ruleName,[string]$exePath)"
"a powershell script of 70 lines"
).arg(ruleName, exePath);

// Construct the PowerShell command with the script
QString command = QString("powershell.exe -Command \"%1\"").arg(checkFirewallScript);

// Convert the command to a wide string
LPCWSTR commandC = (const wchar_t*) command.utf16();

// Use ShellExecute to run the command as administrator
HINSTANCE exitCode = ShellExecute(NULL, L"runas", L"powershell.exe", commandC, NULL, SW_SHOWNORMAL);

My exit code is always 42 though I read where above 32, it is just the instance number. I get a warning within the Qt error box of "QString::arg: 2 argument(s) missing in" and it spits out the script. I have no percent signs and the script works in powershell by itself (when I add the 2 "missing" arguments directly to script). This also worked without issue when I was using Qt Process, but I want UAC to be invoked so I am using ShellExecute (which is ShellExecuteW according to headers).


Solution

  • // Get the application file path and name
    QString ruleName = QCoreApplication::applicationName();
    QString exePath = QCoreApplication::applicationFilePath();
    
    // Define the embedded PowerShell scripts as strings
    const QString checkFirewallScript = R"(param(
            [Parameter(Mandatory=$true)][string]$ruleName,
            [Parameter(Mandatory=$true)][string]$exePath
        )
        $rules = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue |
        ...
    )";
    
    // Construct the command to run PowerShell with the script
    QString command = QString("-ExecutionPolicy Bypass -F \"%1\" -ruleName \"%2\" -exePath \"%3\"")
                          .arg(tempScriptPath)
                          .arg(ruleName)
                          .arg(exePath);
    
    // Convert the command to a wide string
    std::wstring commandW = command.toStdWString();
    
    // Initialize the SHELLEXECUTEINFO structure
    SHELLEXECUTEINFOW sei = {0};
    sei.cbSize = sizeof(SHELLEXECUTEINFOW);
    sei.fMask = SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS;
    sei.hwnd = NULL;
    sei.lpVerb = L"runas";
    sei.lpFile = L"powershell.exe";
    sei.lpParameters = commandW.c_str();;
    sei.lpDirectory = NULL;
    sei.nShow = SW_SHOWNORMAL;
    sei.hInstApp = NULL;
    
    //run
    if (!ShellExecuteExW(&sei)) { ...
    
    WaitForSingleObject(sei.hProcess, 10000);    // 10 seconds
    DWORD exitCode;
    GetExitCodeProcess(sei.hProcess, &exitCode);
    CloseHandle(sei.hProcess);
    
    if (exitCode == 18000) {
    

    It took a bit of doing, had to forgo running the entire script as an argument because I kept encountering too many issues. This lets me run powershell in its own window while also requesting admin permissions to do some firewall maintenance. Thanks to
    IInspectable for reccomending ShellExecuteExW, I can do custom error codes in the script. I didn't post the entire script + function, but this represents the good parts.