我对一个类进行了单元测试,该类旨在以提升的权限运行 shell 命令。一个简单的测试用例是将测试文件复制到
C:\Program Files
。该类调用 C 函数 ShellExecuteExW
函数,并在 lpVerb = L"runas"
参数中设置 lpFile = L"cmd"
和 SHELLEXECUTEINFOAW
。调用成功,没有任何错误,UAC 提升提示中的详细信息都是正确的,但是当我在程序文件中查找测试文件时,它不在那里。
我使用的是 Eiffel 编译器,它生成看起来神秘的 MSVC 编译的 C 代码,但这就是如果用 C 手动编写的代码的样子。请注意,在实际代码中,
%TEMP%
已经展开了。
#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;
}
我运行单元测试,得到预期的提升提示,并且命令的所有详细信息都是正确的。
单击“确定”后,命令返回且没有错误,但测试文件未按预期复制到
C:\Program Files
。如果我使用管理员权限手动打开 shell 并复制/粘贴完全相同的命令,则会成功并且文件实际上被复制。所以我很困惑为什么这在 C 调用中不起作用。
Eiffel 单元测试调用
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
测试在检查文件是否已复制时失败。我还手动检查了文件资源管理器。
替代方法
为了找到解决方法,我也在尝试替代功能:(但结果是相同的)
HINSTANCE ShellExecute (
_In_opt_ HWND hwnd,
_In_opt_ LPCTSTR lpOperation,
_In_ LPCTSTR lpFile,
_In_opt_ LPCTSTR lpParameters,
_In_opt_ LPCTSTR lpDirectory,
_In_ INT nShowCmd
);
环境
适用于 x64 的 Microsoft (R) C/C++ 优化编译器版本 16.00.40219.01
操作系统:Windows 7
问题是,显然当 cmd 启动提升时,它不支持当前目录,而是使用 system32 文件夹:https://stackoverflow.com/a/38034535/179634
因此,您需要在参数中指定
copy
命令的完整路径,或者使用 cd
命令更改命令行中的当前目录。
正如 Luke 指出的,当
cmd
启动时,它不会遵循当前目录,而是使用 system32 文件夹。卢克提供的解决方案非常有帮助,但并没有完全解决问题,正如我将解释的那样。我尝试使用这个命令部分序列:
调试器视图
当我手动检查输出文件夹时,我可以看到
file.txt
,但单元测试仍然失败。为什么会这样?事实证明 ShellExecute
是异步运行的,所以我需要某种方法来等待该过程完成。我想出的解决方案如下。 (请注意任何带有“c_”前缀的内容都是来自 Eiffel 的 C 调用)
class
EL_WINDOWS_SHELL_COMMAND
inherit
EL_ALLOCATED_C_OBJECT
rename
make_default as make
redefine
make
end
feature -- Basic operations
execute
local
n: INTEGER
do
c_set_n_show (self_ptr, show_type)
if c_shell_execute (self_ptr) then
-- make asynchronous process appear to be synchronous
n := c_wait_for_single_object (process_handle)
if c_close_handle (process_handle) then
do_nothing
end
is_successful := n >= 0
end
end
feature {NONE} -- Implementation
process_handle: NATURAL
do
Result := c_process (self_ptr)
end
end
Class
EL_WINDOWS_SHELL_COMMAND
是 C 结构的包装
SHELLEXECUTEINFOW
。
自我提醒
我需要要求 Stackoverflow 改进 Eiffel 的语法突出显示