为什么 'Run as administrator' 会(有时)更改批处理文件的当前目录?

Why does 'Run as administrator' change (sometimes) batch file's current directory?

我有一个批处理文件,它与我想要的文件位于同一目录中 xcopy。但由于某种原因找不到文件。

我认为当前目录始终是批处理文件所在的位置。

我运行 以管理员身份批处理文件。这发生在 Windows 7 64 位台式计算机上。

批处理文件:

@ECHO OFF
XCOPY /y "File1.txt" "File2.txt"
PAUSE

错误:

File not found - File1.txt
0 File(s) copied

错误消息非常容易解释。找不到文件 file1.txt

由于文件名不包含绝对路径,系统尝试在当前目录下查找。您的当前目录不包含此文件。

您的误解是当前目录不是包含bat文件的目录。这是两个不相关的概念。

你可以在你的bat文件中添加这两个命令来轻松检查

echo BAT directory is %~dp0
echo Current directory  is %CD%

您可以注意到它们是不同的,最后一个反斜杠是否附加的方式存在细微差别。

所以,基本上有两种方法可以解决这个问题

  1. 要么更改当前目录以匹配预期目录

    pushd %~dp0
    XCOPY /y "File1.txt" "File2.txt"
    popd
    
  2. 或在命令中指定完整路径

    XCOPY /y "%~dp0File1.txt" "%~dp0File2.txt"
    

以管理员身份运行启动批处理文件时哪个目录是当前工作目录取决于用户帐户控制 当前用户的 (UAC) 设置。

这可以用下面的小批处理文件来演示 C:\Temp\Test.bat:

@echo Current directory is: %CD%
@pause

用户帐户控制设置中选择

Default - Notify me only when programs try to make changes to my computer

  • Don't notify me when I make changes to Windows settings

并使用 运行 作为管理员,Windows 使用注册表项

HKEY_CLASSES_ROOT\batfile\shell\runasuser\command

此注册表项不包含用于执行批处理文件的默认字符串。取而代之的是带有 CLSID {ea72d00e-4960-42fa-ba92-7792a7944c1d}.

的字符串值 DelegateExecute

结果是打开一个对话框 window,标题为 用户帐户控制,文本为:

Do you want to allow the following program to make changes to this computer?

Program name: Windows Command Processor
Verified publisher: Microsoft Windows

用户确认后,Windows临时打开一个新用户session,就像在命令行上使用时一样RunAs

在这个新用户 session 中,当前工作目录是 %SystemRoot%\System32 现在执行 Windows 注册表中定义的命令,默认字符串为键

HKEY_CLASSES_ROOT\batfile\shell\runas\command

即:

%SystemRoot%\System32\cmd.exe /C "%1" %*

因此控制台 window 打开,标题为 C:\Windows\System32\cmd.exe 和两行:

Current directory is: C:\Windows\System32
Press any key to continue . . .

按下任意键后,批处理完成,导致关闭 cmd.exe,导致关闭用户 session。


但是在用户帐户控制设置

中选择

Never notify me when

  • Programs try to install software or make changes to my computer

  • I make changes to Windows settings

行为不同,因为用户已经提升了权限。

现在Windows直接使用命令

%SystemRoot%\System32\cmd.exe /C "%1" %*

根据key的默认字符串

HKEY_CLASSES_ROOT\batfile\shell\runas\command

当前用户session.

结果是打开一个控制台 window,标题也是 C:\Windows\System32\cmd.exe,但在 window 中显示的是:

Current directory is: C:\Temp
Press any key to continue . . .

parent 进程的当前工作目录(Windows Explorer 作为桌面)用于执行批处理文件,因为不需要切换到不同的用户 session这种情况。


PA 在他的回答中已经发布了 2 个可能的解决方案,我在这里复制了一个小改进(pushd 目录用双引号引起来)并添加了第三个。

  1. 使用 pushdpopd:

    将当前目录更改为批处理文件的目录
    pushd "%~dp0"
    %SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
    popd
    

    这也适用于 UNC 路径。 运行 在命令提示符 window pushd /? 中解释为什么这也适用于 UNC 路径。

  2. 在源和目标规范中使用批处理文件的目录:

    %SystemRoot%\System32\xcopy.exe "%~dp0File1.txt" "%~dp0File2.txt" /Y
    
  3. 使用 cd:

    将工作目录更改为批处理文件的目录
    cd /D "%~dp0"
    %SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
    

    这不适用于 UNC 路径,因为命令解释器 cmd 默认不支持将 UNC 路径作为当前目录,请参见示例 CMD does not support UNC paths as current directories 了解详细信息。

为了完整性和模糊性,我添加了另一个解决方法,确认在 Windows 8.1 下工作并且预计在其他地方工作,因为它依赖于记录的功能:

您可以更改 runas 命令定义键
HKEY_CLASSES_ROOT\batfile\shell\runas\command
HKEY_CLASSES_ROOT\cmdfile\shell\runas\command 进入

%SystemRoot%\System32\cmd.exe /S /C "(for %%G in (%1) do cd /D "%%~dpG") & "%1"" %*

这导致 batcmd 文件在使用 runas 动词启动时从其包含目录开始,分别是 "Run as Administrator" 菜单项。

原始命令的新增内容究竟是做什么的:

  • cmd /S/C
  • 之后去除命令字符串中的第一个和最后一个(双)引号
  • for %%G in (%1) do 枚举其单个条目,即 %1 参数, 使其在循环体中可扩展为 %%G;这封信是任意的,但有些可能是 "reserved"
  • %%~dpG 扩展到 drive 和 %%Gpath,~ 波浪号剥离引号 如果 存在,这就是为什么我们明确地添加它们
  • cd /Dd 驱动和目录都更改为其参数,最后
  • & 运行第二个命令 "%1" %* 而不管第一个命令是否成功。

可以 使用 pushd,它甚至支持 UNC 路径,但是一个杂散的 popd 会在 system32 目录中登陆任何脚本,这不是我喜欢的行为。

应该 也可以对 exefile 条目执行此操作,但坦率地说,我宁愿忍受这种不一致,也不愿在我的网站上尝试这样做系统,因为那里的任何错误都可能破坏很多。

享受打败操作系统安全机制的乐趣 :)