为什么“以管理员身份运行”有时会更改批处理文件的当前目录?

18

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

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

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

批处理文件:

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

错误:

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

如果我使用完整路径,xcopy 就可以工作,但仅使用文件名就不行。 - mcu
DIR添加到批处理文件中,在屏幕上转储了一堆文件 - 看起来像是 system32 目录。 - mcu
尝试使用以下命令进行复制:xcopy /y "%~dp0File1.txt" "%~dp0 File2.txt" - wOxxOm
这个可行。这与提供完整路径相同。 - mcu
3个回答

31

使用上下文菜单项以管理员身份运行批处理文件时,当前工作目录取决于当前用户的用户账户控制(UAC)设置。

可以通过以下小批处理文件 C:\Temp\Test.bat 进行演示:

@echo Current directory is: %CD%
@pause

用户账户控制设置中选择了以下选项:

默认 - 只在程序试图更改计算机时通知我

  • 当我更改Windows设置时不要通知我

并使用以管理员身份运行,Windows会使用注册表键。

HKEY_CLASSES_ROOT\batfile\shell\runasuser\command

该注册表键不包含用于执行批处理文件的默认字符串。相反,它具有带有CLSID {ea72d00e-4960-42fa-ba92-7792a7944c1d}的字符串值DelegateExecute

结果是打开一个标题为用户账户控制的对话框,文本如下:

您要允许以下程序对计算机进行更改吗?

程序名称:Windows命令处理器
已验证发布者:Microsoft Windows

经过用户确认后,Windows会暂时打开一个新的用户会话,就像在命令行上使用RunAs一样。

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

HKEY_CLASSES_ROOT\batfile\shell\runas\command

即:

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

因此,将打开一个标题为C:\Windows\System32\cmd.exe的控制台窗口,并显示2行:

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

按下任意键后,批处理执行就会结束,导致关闭 cmd.exe,从而关闭用户会话。


但如果在用户帐户控制设置中选择了以下选项:

不要在发生以下情况时通知我:

  • 程序尝试安装软件或更改计算机设置

  • 我更改了 Windows 设置

则行为会有所不同,因为用户已经拥有提升的特权。

现在 Windows 直接使用以下命令:

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

根据默认键的字符串

HKEY_CLASSES_ROOT\batfile\shell\runas\command

当前用户会话中。

结果是打开一个控制台窗口,窗口标题为C:\Windows\System32\cmd.exe,但显示的内容是:

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

由于在这种情况下不需要切换到不同的用户会话,因此父进程(桌面的Windows资源管理器)的当前工作目录用于执行批处理文件。


PA 在他的回答中已经发布了2个可能的解决方案,我在此复制他的回答并进行了小改进(使用带引号的目录路径的pushd),并增加了第三个解决方案。

  1. 使用 pushdpopd 将当前目录更改为批处理文件所在的目录:

    pushd "%~dp0"
    %SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
    popd
    

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

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

  3. %SystemRoot%\System32\xcopy.exe "%~dp0File1.txt" "%~dp0File2.txt" /Y
    
  4. 使用cd命令将工作目录更改为批处理文件所在的目录:

  5. cd /D "%~dp0"
    %SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
    

    因为命令解释器cmd默认情况下不支持将UNC路径作为当前目录,所以此方法无法用于UNC路径。有关详细信息,请参见CMD does not support UNC paths as current directories


3

为了完整性和隐蔽性,我另外提供一种解决方法,并已在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"" %*

这会导致使用runas动词启动时,batcmd文件分别从它们所在的目录开始。 "以管理员身份运行"菜单项。

原始命令的添加内容的确切作用:

  • cmd /S剥离了/C后面命令字符串的第一个和最后一个(双引号)
  • for %%G in (%1) do枚举其单个条目,%1参数可以作为循环体中的%%G进行扩展;字母是任意的,但有些字母可能是“保留的”。
  • %%~dpG扩展到%%Gdrive和path;如果存在引号,则~波浪号将其去除,这就是为什么我们需要明确地添加引号的原因。
  • cd /D将驱动器和目录都更改为其参数,最后
  • &运行第二个命令"%1" %*,而不管第一个命令的成功与否。

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

这也可以对exefile条目进行操作,但说实话,与其尝试更改我的系统,我宁愿接受不一致性。

享受打败操作系统安全机制的过程 :)


2
错误信息很明显。找不到文件file1.txt
由于文件名没有包含绝对路径,系统会在当前目录下查找它。您的当前目录中没有此文件。
您的误解是当前目录不是包含批处理文件的目录。这两个概念无关。
您可以通过将以下两个命令添加到批处理文件中轻松检查。
echo BAT directory is %~dp0
echo Current directory  is %CD%

你可以注意到它们是不同的,并且在最后一个反斜杠被附加或未被附加的方式上存在微妙的差异。
因此,基本上有两种方法来处理这个问题。
  1. either change the current directory to match the expected one

    pushd %~dp0
    XCOPY /y "File1.txt" "File2.txt"
    popd
    
  2. or specify the full path in the command

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

很好,但是为什么它在某些计算机上可以工作而在另一些计算机上却不行呢?根据这个答案的说法,工作目录应该是批处理文件启动的目录。我通过右键单击批处理文件然后选择“以管理员身份运行”来启动它。 - mcu
好的,我刚刚发现 '以管理员身份运行' 是导致当前目录从批处理文件位置更改为 c:\windows\system32 的罪魁祸首。然而,我在另一台计算机上以管理员身份运行了同样的批处理文件,显然它没有更改那里的 CD,因为它可以正常工作。所以这不是一致的。 - mcu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接