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.
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
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.