如何调试混合 cmd 和 powershell 脚本以解决特殊字符问题,如 & 和

Posted

技术标签:

【中文标题】如何调试混合 cmd 和 powershell 脚本以解决特殊字符问题,如 & 和【英文标题】:how to debug mixed cmd and powershell scripts to solve special character problems like ampersand & 【发布时间】:2021-11-24 05:26:47 【问题描述】:

我正在创建一个 dos 批处理脚本 (cmd.exe) .cmd,它通过 powershell 将自己提升为管理员。

但如果它存储在名称中同时包含空格和 & 符号的文件夹中,它就不起作用了。

我不是专家,所以我搜索了网络,但找不到解决问题的解决方案或文档。所以我也想知道如何调试这种特殊情况和问题的可能解决方案,以及在哪里可以找到描述解决方案的文档或到达那里的步骤..(如果有的话)。

重现问题:我在桌面下创建了一个名为“Pie & tea”的文件夹,没有引号。在文件夹中,我创建了一个脚本“test.cmd”,在其中我只放了重现问题的必要条件。这是脚本的内容:

@echo off

pause

PowerShell start "%~f0" -verb runas

pause

goto: eof

我通过双击来运行脚本(这是所需的模式)。此代码显示错误(我使用翻译器从意大利语翻译):

Press any key to continue. . .
In row: 1 car: 34
+ start C:\Users\fposc\Desktop\Pie & tea\test.cmd -verb runas
+                                  ~
Ampersand (&) not allowed. The & operator is reserved for future use. Use "&" to pass the ampersand as
string.
    + CategoryInfo: ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId: AmpersandNotAllowed

Press any key to continue. . .

并按照一些说明,我通过将双引号增加了三倍,使用 powershell 修改了代码行:

PowerShell start """%~f0""" -verb runas

但是 cmd 窗口打开,在按下空格后 UAC 启动并在同意后另一个窗口出现了一会儿,但它立即关闭,我看不到任何错误

我在网上看到任何使用单引号' 或powershell 转义字符` 或dos 批处理的插入符号^ 或powershell 的--% 参数等的解决方案。 但我无法解决。 我没有把所做的尝试放在这里,因为我以一种愚蠢的方式复制(因为我不熟悉混合 cmd / powershell 操作)并且我结合各种解决方案进行了无数次随机尝试。没有任何积极的结果。

我还看到了我想评估的解决方案,例如:

    更改目录并使用相对路径。 准备字符串变量并替换字符。 使用 powershell -encodedcommand 传递参数。

但首选的解决方案是解释如何在单行代码PowerShell start "%~f0" -verb runas 上转义字符以及如何到达那里以及参考文档。

访问的一些链接:

VBS Shell.Application and PowerShell Start-Process do not escape ampersands internally

Quoting issues with PowerShell

Issues on windows when the username contains &

Executing PowerShell script via explorer context menu on items containing ampersands in their names

***: batch file deal with Ampersand (&) in folder name

***: How to access file paths in PowerShell containing special characters

***: How to pass strings to powershell from cmd with ampersands in values?


【问题讨论】:

使用-File参数后是否还有同样的效果? PowerShell -File "%~f0" @Abraham Zinala。我试过了,但 -File 参数说它只接受 powershell .ps1 脚本。该脚本是一个 .cmd,并给出错误:Unable to process -File 'C:\Users\fposc\Desktop\Pie & tea\test.cmd'. The file has no 'ps1' extension. Please provide a valid Windows PowerShell script file name and try again. 我不知道如何使用-File 提升 为什么要用cmd启动powershell来启动.bat文件? @Theo,因为我上面写的脚本是用dos批处理写的。 .cmd 脚本检查是否被提升。如果没有提升,它会通过 powershell 自动提升。 【参考方案1】:

调试提示:

如果 PowerShell 端出现问题,您将在调用 cmd.exe 会话中看到错误消息;要打印 PowerShell 本身看到的命令行,请将 [Environment]::CommandLine; 添加到正在传递的命令之前。[1]

要使 PowerShell 的 Start-Process cmdlet(其内置别名为 start)在 保持会话打开cmd.exe 进程中启动批处理文件,请显式调用 cmd.exe,使用cmd /k,并将批处理文件路径传递给它。一旦您确定一切都按预期工作,请将 /k 替换为 /c(创建一个在批处理文件终止时终止的会话)。

您的主要问题

很遗憾,如果批处理文件路径包含 & 字符,cmd.exe 中的错误需要 手动 ^-escaping& 作为空格,可能还有其他cmd.exe 元字符,即使路径整体包含在"..." 中; 此外,似乎完整批处理文件路径必须在调用时使用%~f0 确保):

例如,要调用C:\foo\a & b.cmdcmd /k "C:\foo\a & b.cmd"不够 - 你必须使用cmd /k "C:\foo\a^ ^&^ b.cmd"

下面的命令使用-replace operation 在 PowerShell 代码中执行此^-转义,对%~f0 的值,cmd.exe 扩展为批处理文件的完整路径。

以下解决方案:

使用辅助。环境变量使其在大多数情况下都能正常工作,即使路径包含其他奇异字符,例如 $%'。 还支持在调用时传递参数。

请参阅底部部分了解解决方案的演变,其中包括详细说明。

@echo off

:: Save the full path of this batch file in an aux. environment variable.
set "__THISFILE=%~f0"
:: Save the arguments received in an aux. environment variable.
:: Note: The space after "=" is required for the PowerShell command 
::       to also work if *no* arguments were passed.
set __ARGS= %*

:: Unless already elevated, relaunch this batch file,
:: with arguments passed through.
:: Note: Replace `exit /b` with just `exit` to 
::       close the current console window after relaunching.
net session >NUL 2>NUL || (powershell.exe -noprofile -c "Start-Process -Verb RunAs cmd /k, ($env:__THISFILE -replace '[ &%%^]', '^$&'), $env:__ARGS" & exit /b)

:: Reset the aux. variables.
set __ARGS=
set __THISFILE=

echo Now running with elevation. Arguments received:
echo.  %*

上述解决方案的演变

如果您的批处理文件路径不包含 ' 字符:

嵌入的'...'引用在下面的PowerShell命令中使用,以避免转义嵌入的"引号,这可能会变得棘手;这假设批处理文件路径本身不包含 ' 字符(如果您的路径确实包含 ' 字符,请参见下文。)

@echo off

:: Unless already elevated, relaunch this batch file with elevation.
net session >NUL 2>NUL || (powershell.exe -c "Start-Process -Verb RunAs cmd /k, ('%~f0' -replace '[ &]', '^$&')" & goto :eof)

echo Now running with elevation.

注意:

net session >NUL 2>NUL 是一个简单的技巧,用于测试当前进程是否已提升(以管理员身份运行):它仅在提升的会话中成功(反映在退出代码 0 中,|| 和 @987654362 @ 运算符隐式作用)。

-c (-Command) 明确表示正在将命令传递给powershell.exe;虽然不是绝对必要,但养成一个好习惯,因为pwsh.exePowerShell (Core) 7+ CLI,现在需要它(请参阅链接的 CLI下面的文档)。

/k, ... 创建一个 array 参数,这些参数按位置传递给Start-Process-ArgumentList 参数 - 本质上,Start-Process 从可执行文件名称构建一个命令行,@ 987654371@,所有参数都由字符串连接并在后台使用空格 - 有关详细信息,请参阅您的后续问题的this answer。


如果您的批处理文件路径包含 ' 字符,则必须使用嵌入的 "..." 引用

使用powershell.exeWindows PowerShell CLI,最强大的方法 - 这里确实需要 - 使用"^""(原文如此)来转义每个嵌入",如下图。

(相比之下,pwsh.exePowerShell (Core) 7+ CLI 现在幸运地接受 ""。)

net session >NUL 2>NUL || (powershell.exe -nop -c "Start-Process -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[ &]', '^$&')" & goto :eof)

如果您的批处理文件路径包含 $ 字符。 ' chars.,一个辅助。必须使用环境变量

$ 字符。如果您使用嵌入式'...' 引用,单独使用不会有问题,但如果' 也存在,则嵌入"..." 引用不是一个选项,因为PowerShell 会处理$ 字符。作为变量引用的开始。

查看顶部的解决方案。


如果您还想支持将参数传递给批处理文件

注意:顶部的解决方案现在采用了这种方法,同时还处理了更广泛的奇异批处理文件路径。

为了防止进一步引用令人头疼的问题,辅助。环境变量__ARGS 在下面的代码中用于存储批处理文件接收到的所有参数 (%*),然后 PowerShell 代码可以在重新调用时通过 $env:__ARGS 访问这些参数。

@echo off

:: Save the arguments received in an aux. environment variable.
:: Note: The space after "=" is required for the PowerShell command 
::       to also work if *no* arguments were passed.
set __ARGS= %*

:: Unless already elevated, relaunch this batch file,
:: with arguments passed through.
net session >NUL 2>NUL || (powershell.exe -nop -c "Start-Process -Verb RunAs cmd /k, ("^""%~f0"^"" -replace '[ &]', '^$&'), $env:__ARGS" & goto :eof)

:: Reset the aux. variable.
set __ARGS=

echo Now running with elevation. Arguments received:
echo.  %*

[1] 一个简单的例子(来自cmd.exe的调用):powershell -c "[Environment]::CommandLine; '%OS%'"

【讨论】:

以上是关于如何调试混合 cmd 和 powershell 脚本以解决特殊字符问题,如 & 和的主要内容,如果未能解决你的问题,请参考以下文章

powershell怎么运行

Jenkins入门之执行Powershell脚本

如何通过 powershell 或 cmd 计算屏幕数量

我无法在 CMD 和 powershell 中执行 C 程序

如何编写使用 powershell 而不是 cmd 的控制台应用程序

如何使用 cmd / powershell 对文件夹中的所有程序执行 taskkill