winapielevated-privilegesrunasshellexecuteex

ShellExecuteExW function with lpVerb = L"runas" and lpFile = L"cmd" succeeds but fails


I have a unit test for a class that is intended to run shell commands with elevated privileges. A simple test case is to copy a test file into C:\Program Files. The class makes a call to the C function ShellExecuteExW function with lpVerb = L"runas" and lpFile = L"cmd" set in the SHELLEXECUTEINFOAW argument. The call succeeds without any errors and the details in the UAC elevation prompt are all correct, but when I look for the test file in program files, it is not there.

I am using an Eiffel compiler that generates cryptic looking MSVC compiled C code, but this is what the code would look like if manually written in C. Note that in the actual code, %TEMP% is already expanded.

#include <windows.h>
#include <Shellapi.h>

int main() {
    SHELLEXECUTEINFOAW shExecInfo = { 0 };

    // Set the structure size
    shExecInfo.cbSize = sizeof(SHELLEXECUTEINFOAW);

    // Specify the operation to perform
    shExecInfo.lpVerb = L"runas"; // "runas" elevates the privileges

    // Specify the file or folder to be copied
    shExecInfo.lpFile = L"cmd";

    // Specify the parameters (in this case, the copy command)
    shExecInfo.lpParameters = L"/C /U copy data\\txt\\file.txt \"C:\\Program Files\" 2> %TEMP%\\copy-errors.txt";

    // Specify the working directory
    shExecInfo.lpDirectory = NULL;

    // Set the show property
    shExecInfo.nShow = SW_HIDE;

    // Execute the operation
    if (ShellExecuteExW(&shExecInfo) == FALSE) {
        // Handle error
        DWORD dwError = GetLastError();
        if (dwError == ERROR_CANCELLED) {
            // The user refused the UAC prompt
            printf("User refused the UAC prompt.\n");
        } else {
            // Other error
            printf("Error: %lu\n", dwError);
        }
        return 1;
    }

    return 0;
}

I run the unit test and I get the expected elevation prompt, and all the details of the command are correct.

UAC prompt

enter image description here

After clicking OK, the command returns without error, but the test file is not being copied into C:\Program Files as expected. If I manually open a shell with admin privileges and copy/paste the exact same command, it succeeds and the file is actually copied. So I am mystified why this is not working in the C call.

The Eiffel Unit test which calls ShellExecuteExW

test_admin_level_execution
   -- OS_COMMAND_TEST_SET.test_admin_level_execution
   note
      testing: "[
         covers/{EL_WINDOWS_ADMIN_SHELL_COMMAND}.execute,
         covers/{EL_WINDOWS_ADMIN_SHELL_COMMAND}.set_command_and_parameters
      ]"
      status: "[
         Commented out in `make_named'. Failing on Windows
      ]"
   local
      command: EL_OS_COMMAND_I; destination_path: FILE_PATH
   do
      if Executable.Is_work_bench then
         create {EL_COPY_FILE_COMMAND_IMP} command.make ("data/txt/file.txt", Directory.applications)
         command.administrator.enable
         command.execute
         assert ("successful", not command.has_error)
         destination_path := Directory.applications + "file.txt"
         if destination_path.exists then
            create {EL_DELETE_FILE_COMMAND_IMP} command.make (destination_path)
            command.administrator.enable
            command.execute
            assert ("File deleted", not destination_path.exists)
         else
            failed ("File exists")
         end
      end
   end

The test fails at the point it checks to see if the file is copied. I have also manually checked in File Explorer.

Alternative Method

In an attempt to find a workaround I am also trying an alternate function: (but the outcome is the same)

    HINSTANCE ShellExecute (
        _In_opt_ HWND    hwnd,
        _In_opt_ LPCTSTR lpOperation,
        _In_     LPCTSTR lpFile,
        _In_opt_ LPCTSTR lpParameters,
        _In_opt_ LPCTSTR lpDirectory,
        _In_     INT     nShowCmd
    );

Environment

Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64

OS: Windows 7


Solution

  • The problem is that apparently when cmd is launched elevated it does not honor the current directory and instead uses the system32 folder: https://stackoverflow.com/a/38034535/179634

    So you need to either specify the full path in the parameter to the copy command, or change the current directory in your command line using the cd command.