Don*_*nce 19 windows powershell scripting cmd batch-file
如何将PowerShell脚本嵌入到与Windows批处理脚本相同的文件中?
我知道在其他情况下这种事情是可能的:
sqlcmd
并在文件开头巧妙地安排goto和注释#!/usr/local/bin/python
.可能没有办法做到这一点 - 在这种情况下,我将不得不从启动脚本调用单独的PowerShell脚本.
我考虑过的一个可能的解决方案是回显PowerShell脚本,然后运行它.不这样做的一个很好的理由是,尝试这个的部分原因是使用PowerShell环境的优点而没有例如转义字符的痛苦
我有一些不寻常的限制,并希望找到一个优雅的解决方案.我怀疑这个问题可能是各种各样的反应:"你为什么不尝试解决这个不同的问题." 我只想说这些是我的约束,对此感到抱歉.
有任何想法吗?是否有一个巧妙的评论和转义字符的合适组合,这将使我能够实现这一目标?
关于如何实现这一点的一些想法:
^
一行中的克拉是一个延续 - 就像Visual Basic中的下划线一样&
通常用于将命令echo Hello & echo World
结果分成两个回声在不同的行上所以这样的事情(如果我可以使它工作)会很好:
# & call powershell -psconsolefile %0
# & goto :EOF
/* From here on in we're running nice juicy powershell code */
Write-Output "Hello World"
Run Code Online (Sandbox Code Playgroud)
除了...
Windows PowerShell console file "insideout.bat" extension is not psc1. Windows PowerShell console file extension must be psc1.
'#', it is not recognized as an internal or external command, operable program or batch file.
Car*_*rez 18
这个只传递给PowerShell的正确行:
dosps2.cmd
:
@findstr/v "^@f.*&" "%~f0"|powershell -&goto:eof
Write-Output "Hello World"
Write-Output "Hello some@com & again"
Run Code Online (Sandbox Code Playgroud)
在正则表达式排除了线路开始@f
和包括&
并通过一切来的PowerShell.
C:\tmp>dosps2
Hello World
Hello some@com & again
Run Code Online (Sandbox Code Playgroud)
Jay*_*uzi 12
听起来你正在寻找有时被称为"多语言脚本"的东西.对于CMD - > PowerShell,
@@:: This prolog allows a PowerShell script to be embedded in a .CMD file.
@@:: Any non-PowerShell content must be preceeded by "@@"
@@setlocal
@@set POWERSHELL_BAT_ARGS=%*
@@if defined POWERSHELL_BAT_ARGS set POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"%
@@PowerShell -Command Invoke-Expression $('$args=@(^&{$args} %POWERSHELL_BAT_ARGS%);'+[String]::Join([char]10,$((Get-Content '%~f0') -notmatch '^^@@'))) & goto :EOF
Run Code Online (Sandbox Code Playgroud)
如果你不需要支持引用的参数,你甚至可以使它成为一个单行:
@PowerShell -Command Invoke-Expression $('$args=@(^&{$args} %*);'+[String]::Join([char]10,(Get-Content '%~f0') -notmatch '^^@PowerShell.*EOF$')) & goto :EOF
Run Code Online (Sandbox Code Playgroud)
取自http://blogs.msdn.com/jaybaz_ms/archive/2007/04/26/powershell-polyglot.aspx.那是PowerShell v1; 它在v2中可能更简单,但我没看过.
如果您不介意 PowerShell 一开始出现一个错误,这似乎可行:
dosps.cmd
:
@powershell -<%~f0&goto:eof
Write-Output "Hello World"
Write-Output "Hello World again"
Run Code Online (Sandbox Code Playgroud)
这里讨论了这个话题。主要目标是避免使用临时文件以减少缓慢的I / O操作,并在没有冗余输出的情况下运行脚本。
这是根据我的最佳解决方案:
<# :
@echo off
setlocal
set "POWERSHELL_BAT_ARGS=%*"
if defined POWERSHELL_BAT_ARGS set "POWERSHELL_BAT_ARGS=%POWERSHELL_BAT_ARGS:"=\"%"
endlocal & powershell -NoLogo -NoProfile -Command "$input | &{ [ScriptBlock]::Create( ( Get-Content \"%~f0\" ) -join [char]10 ).Invoke( @( &{ $args } %POWERSHELL_BAT_ARGS% ) ) }"
goto :EOF
#>
param(
[string]$str
);
$VAR = "Hello, world!";
function F1() {
$str;
$script:VAR;
}
F1;
Run Code Online (Sandbox Code Playgroud)
更好的方法(在此处看到):
<# : batch portion (begins PowerShell multi-line comment block)
@echo off & setlocal
set "POWERSHELL_BAT_ARGS=%*"
echo ---- FROM BATCH
powershell -noprofile -NoLogo "iex (${%~f0} | out-string)"
exit /b %errorlevel%
: end batch / begin PowerShell chimera #>
$VAR = "---- FROM POWERSHELL";
$VAR;
$POWERSHELL_BAT_ARGS=$env:POWERSHELL_BAT_ARGS
$POWERSHELL_BAT_ARGS
Run Code Online (Sandbox Code Playgroud)
其中POWERSHELL_BAT_ARGS
是命令行参数首先设置为在间歇部变量。
技巧是在批处理重定向优先级中-该行将<# :
像一样进行解析:<#
,因为重定向的优先级高于其他命令。
但是:
以批处理文件开头的行被视为标签-即未执行。但这仍然是有效的PowerShell注释。
剩下的唯一一件事就是找到PowerShell读取和执行的正确方法,%~f0
这是cmd.exe执行的脚本的完整路径。
还要考虑这个“多语言”包装脚本,它支持嵌入式 PowerShell 和/或 VBScript/JScript 代码;它改编自作者本人flabdablet于 2013 年发布的巧妙原创文章,但由于仅提供链接答案,该答案已于 2015 年被删除。
一个改进凯尔优秀答案的解决方案:
sample.cmd
创建一个包含以下内容的批处理文件(例如):
<# ::
@echo off & setlocal
set "__thisBatchFile=%~f0"
copy /y "%~f0" "%TEMP%\%~n0.ps1" >NUL && powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\%~n0.ps1" %*
set ec=%ERRORLEVEL% & del "%TEMP%\%~n0.ps1"
exit /b %ec%
#>
# Paste arbitrary PowerShell code here.
# In this example, all arguments are echoed.
# Use $env:__thisBatchFile to determine this batch file's full path.
'Args:'
$Args | % { 'arg #{0}: [{1}]' -f ++$i, $_ }
Run Code Online (Sandbox Code Playgroud)
笔记:
*.ps1
会在文件夹中创建一个临时文件,稍后清理%TEMP%
;这样做大大简化了(合理地)稳健地传递参数,只需使用%*
powershell
为。pwsh
该技术的解释:
Line<# ::
是一个混合行,PowerShell 将其视为注释块的开始,但会忽略它,这是一种从npocmaka 的答案cmd.exe
借用的技术。
因此,以 开头的批处理文件命令@
将被 PowerShell 忽略,但由cmd.exe
;执行。由于最后一个@
- 前缀行以 结尾exit /b
(它立即退出批处理文件),因此cmd.exe
忽略文件的其余部分,因此可以自由包含非批处理文件代码,即 PowerShell 代码。
该#>
行结束包含批处理文件代码的 PowerShell 注释块。
由于该文件作为一个整体是一个有效的 PowerShell 文件,因此不需要任何findstr
技巧来提取 PowerShell 代码;但是,由于 PowerShell 仅执行文件扩展名为 的脚本.ps1
,因此必须创建批处理文件的(临时)副本;在以批处理文件 ( ) 命名的文件夹%TEMP%\%~n0.ps1
中创建临时副本,但使用扩展名;完成后,临时文件将自动删除。%TEMP%
%~n0
.ps1
请注意,需要 3 行单独的语句cmd.exe
才能传递 PowerShell 命令的退出代码。
(假设使用setlocal enabledelayedexpansion
允许将其作为单行执行,但这可能会导致对!
参数中的 chars. 进行不必要的解释。)
为了证明参数传递的稳健性:
假设上面的代码已保存为sample.cmd
,调用它为:
sample.cmd "val. w/ spaces & special chars. (\|<>'), on %OS%" 666 "Lisa \"Left Eye\" Lopez"
Run Code Online (Sandbox Code Playgroud)
产生类似以下内容:
<# ::
@echo off & setlocal
set "__thisBatchFile=%~f0"
copy /y "%~f0" "%TEMP%\%~n0.ps1" >NUL && powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\%~n0.ps1" %*
set ec=%ERRORLEVEL% & del "%TEMP%\%~n0.ps1"
exit /b %ec%
#>
# Paste arbitrary PowerShell code here.
# In this example, all arguments are echoed.
# Use $env:__thisBatchFile to determine this batch file's full path.
'Args:'
$Args | % { 'arg #{0}: [{1}]' -f ++$i, $_ }
Run Code Online (Sandbox Code Playgroud)
注意如何嵌入"
字符。被传递为\"
.
但是,存在与嵌入字符相关的边缘情况"
。:
:: # BREAKS, due to the `&` inside \"...\"
sample.cmd "A \"rock & roll\" life style"
:: # Doesn't break, but DOESN'T PRESERVE ARGUMENT BOUNDARIES.
sample.cmd "A \""rock & roll\"" life style"
Run Code Online (Sandbox Code Playgroud)
这些困难归因于cmd.exe
有缺陷的参数解析,最终,试图隐藏这些缺陷是毫无意义的,正如flabdablet 在他的出色回答中指出的那样。
正如他所解释的,在序列中使用 (sic)转义以下cmd.exe
元字符可以解决问题:^^^
\"...\"
& | < >
Run Code Online (Sandbox Code Playgroud)
使用上面的例子:
:: # OK: cmd.exe metachars. inside \"...\" are ^^^-escaped.
sample.cmd "A \"rock ^^^& roll\" life style"
Run Code Online (Sandbox Code Playgroud)
小智 5
我非常喜欢Jean-Fran\xc3\xa7ois Larvoire\ 的解决方案,特别是他对参数的处理并将它们直接传递给 powershell 脚本(添加了+1)。
\n\n但它有一个缺陷。由于我没有评论的声誉,因此我将更正作为新的解决方案发布。
\n\n当脚本名称包含 - 字符时,作为双引号中的 Invoke-Expression 参数的脚本名称将不起作用$
,因为这将在加载文件内容之前进行评估。最简单的补救措施是替换双引号:
PowerShell -c ^"Invoke-Expression (\'^& {\' + [io.file]::ReadAllText(\'%~f0\') + \'} %ARGS%\')"\n
Run Code Online (Sandbox Code Playgroud)\n\n就我个人而言,我更喜欢使用get-content
该-raw
选项,因为对我来说,这更像是 powershell:
PowerShell -c ^"Invoke-Expression (\'^& {\' + (get-content -raw \'%~f0\') + \'} %ARGS%\')"\n
Run Code Online (Sandbox Code Playgroud)\n\n但这当然只是我个人的意见。ReadAllText works
非常完美。
为了完整起见,更正后的脚本:
\n\n<# :# PowerShell comment protecting the Batch section\n@echo off\n:# Disabling argument expansion avoids issues with ! in arguments.\nsetlocal EnableExtensions DisableDelayedExpansion\n\n:# Prepare the batch arguments, so that PowerShell parses them correctly\nset ARGS=%*\nif defined ARGS set ARGS=%ARGS:"=\\"%\nif defined ARGS set ARGS=%ARGS:\'=\'\'%\n\n:# The ^ before the first " ensures that the Batch parser does not enter quoted mode\n:# there, but that it enters and exits quoted mode for every subsequent pair of ".\n:# This in turn protects the possible special chars & | < > within quoted arguments.\n:# Then the \\ before each pair of " ensures that PowerShell\'s C command line parser \n:# considers these pairs as part of the first and only argument following -c.\n:# Cherry on the cake, it\'s possible to pass a " to PS by entering two "" in the bat args.\necho In Batch\nPowerShell -c ^"Invoke-Expression (\'^& {\' + (get-content -raw \'%~f0\') + \'} %ARGS%\')"\necho Back in Batch. PowerShell exit code = %ERRORLEVEL%\nexit /b\n\n###############################################################################\nEnd of the PS comment around the Batch section; Begin the PowerShell section #>\n\necho "In PowerShell"\n$Args | % { "PowerShell Args[{0}] = \'$_\'" -f $i++ }\nexit 0\n
Run Code Online (Sandbox Code Playgroud)\n