如何在 Windows 批处理文件中运行 PowerShell 脚本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2609985/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 07:15:51  来源:igfitidea点击:

How to run a PowerShell script within a Windows batch file

windowspowershellbatch-filecmdscripting

提问by Don Vince

How do I have a PowerShell script embedded within the same file as a Windows batch script?

如何在与 Windows 批处理脚本相同的文件中嵌入 PowerShell 脚本?

I know this kind of thing is possible in other scenarios:

我知道这种事情在其他情况下是可能的:

  • Embedding SQL in a batch script using sqlcmdand a clever arrangements of goto's and comments at the beginning of the file
  • In a *nix environment having the name of the program you wish to run the script with on the first line of the script commented out, for example, #!/usr/local/bin/python.
  • 在批处理脚本中嵌入 SQL 使用sqlcmd并巧妙安排文件开头的 goto 和注释
  • 在 *nix 环境中,您希望运行脚本的程序名称在脚本的第一行被注释掉,例如,#!/usr/local/bin/python.

There may not be a way to do this - in which case I will have to call the separate PowerShell script from the launching script.

可能没有办法做到这一点 - 在这种情况下,我将不得不从启动脚本中调用单独的 PowerShell 脚本。

One possible solution I've considered is to echo out the PowerShell script, and then run it. A good reason to notdo this is that part of the reason to attempt this is to be using the advantages of the PowerShell environment without the pain of, for example, escape characters

我考虑过的一种可能的解决方案是回显 PowerShell 脚本,然后运行它。这样做的一个很好的理由是,尝试这样做的部分原因是为了利用 PowerShell 环境的优势,不必担心,例如转义字符

I have some unusual constraints and would like to find an elegant solution. I suspect this question may be baiting responses of the variety: "Why don't you try and solve this different problem instead." Suffice to say these are my constraints, sorry about that.

我有一些不寻常的限制,想找到一个优雅的解决方案。我怀疑这个问题可能会引起各种各样的反应:“你为什么不尝试解决这个不同的问题呢。” 我只想说这些是我的限制,抱歉。

Any ideas? Is there a suitable combination of clever comments and escape characters that will enable me to achieve this?

有任何想法吗?是否有巧妙的注释和转义字符的合适组合可以使我实现这一目标?

Some thoughts on how to achieve this:

关于如何实现这一目标的一些想法:

  • A carat ^at the end of a line is a continuation - like an underscore in Visual Basic
  • An ampersand &typically is used to separate commands echo Hello & echo Worldresults in two echos on separate lines
  • %0 will give you the script that's currently running
  • ^行尾的克拉是延续 - 就像 Visual Basic 中的下划线
  • &符号&通常用于分隔命令,echo Hello & echo World导致在单独的行上产生两个回声
  • %0 会给你当前正在运行的脚本

So something like this (if I could make it work) would be good:

所以这样的事情(如果我能做到的话)会很好:

# & call powershell -psconsolefile %0
# & goto :EOF
/* From here on in we're running nice juicy powershell code */
Write-Output "Hello World"

Except...

除了...

  • It doesn't work... because
  • the extension of the file isn't as per PowerShell's liking: Windows PowerShell console file "insideout.bat" extension is not psc1. Windows PowerShell console file extension must be psc1.
  • CMD isn't really altogether happy with the situation either - although it does stumble on '#', it is not recognized as an internal or external command, operable program or batch file.
  • 它不起作用......因为
  • 文件的扩展名不符合 PowerShell 的喜好: Windows PowerShell console file "insideout.bat" extension is not psc1. Windows PowerShell console file extension must be psc1.
  • CMD 对这种情况也不完全满意 - 尽管它确实偶然发现 '#', it is not recognized as an internal or external command, operable program or batch file.

采纳答案by Carlos Gutiérrez

This one only passes the right lines to PowerShell:

这一行只将正确的行传递给 PowerShell:

dosps2.cmd:

dosps2.cmd

@findstr/v "^@f.*&" "%~f0"|powershell -&goto:eof
Write-Output "Hello World" 
Write-Output "Hello some@com & again" 

The regular expressionexcludes the lines starting with @fand including an &and passes everything else to PowerShell.

正则表达式不包括行开始@f和包括&并通过一切来的PowerShell。

C:\tmp>dosps2
Hello World
Hello some@com & again

回答by Jay Bazuzi

It sounds like you're looking for what is sometimes called a "polyglot script". For CMD -> PowerShell,

听起来您正在寻找有时称为“多语言脚本”的内容。对于 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

If you don't need to support quoted arguments, you can even make it a one-liner:

如果您不需要支持带引号的参数,您甚至可以将其设为单行:

@PowerShell -Command Invoke-Expression $('$args=@(^&{$args} %*);'+[String]::Join([char]10,(Get-Content '%~f0') -notmatch '^^@PowerShell.*EOF$')) & goto :EOF

Taken from http://blogs.msdn.com/jaybaz_ms/archive/2007/04/26/powershell-polyglot.aspx. That was PowerShell v1; it may be simpler in v2, but I haven't looked.

取自http://blogs.msdn.com/jaybaz_ms/archive/2007/04/26/powershell-polyglot.aspx。那是 PowerShell v1;在 v2 中可能更简单,但我没有看过。

回答by npocmaka

Herethe topic has been discussed. The main goals were to avoid the usage of temporary files to reduce the slow I/O operations and to run the script without redundant output.

这里已经讨论了这个话题。主要目标是避免使用临时文件以减少缓慢的 I/O 操作并在没有冗余输出的情况下运行脚本。

And here's the best solution according to me:

这是我认为的最佳解决方案:

<# :
@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;

An even better way (seen here):

一个更好的方法(见这里):

<# : 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

where POWERSHELL_BAT_ARGSare command line arguments first set as variable in the batch part.

其中POWERSHELL_BAT_ARGS命令行参数首先设置为批处理部分中的变量。

The trick is in the batch redirection priority - this line <# :will be parsed like :<#, because redirection is with higher priority than the other commands.

诀窍在于批量重定向优先级 - 此行将<# :被解析为:<#,因为重定向的优先级高于其他命令。

But the lines starting with :in batch files are taken as labels - i.e., not executed. Still this remains a valid PowerShell comment.

但是:以批处理文件开头的行被当作​​标签——即,不执行。这仍然是一个有效的 PowerShell 注释。

The only thing left is to find a proper way for PowerShell to read and execute %~f0which is the full path to the script executed by cmd.exe.

唯一剩下的就是找到一个合适的方式让 PowerShell 读取和执行%~f0,这是 cmd.exe 执行的脚本的完整路径。

回答by Carlos Gutiérrez

This seems to work, if you don't mind one error in PowerShell at the beginning:

这似乎有效,如果您不介意一开始 PowerShell 中的一个错误:

dosps.cmd:

dosps.cmd

@powershell -<%~f0&goto:eof
Write-Output "Hello World" 
Write-Output "Hello World again" 

回答by mklement0

Also consider this "polyglot" wrapper script, which supports embedded PowerShell and/or VBScript/JScript code; it was adapted from this ingenious original, which the author himself, flabdablet, had posted in 2013, but it languished due to being a link-only answer, which was deleted in 2015.

还要考虑这个“多语言”包装脚本,它支持嵌入式 PowerShell 和/或 VBScript/JScript 代码;它改编自作者本人flabdablet于 2013 年发布的这个巧妙的原作,但由于仅提供链接答案,因此在 2015 年被删除。

A solution that improves on Kyle's excellent answer:

改进了凯尔出色答案的解决方案:

<# ::
@setlocal & copy "%~f0" "%TEMP%\%~0n.ps1" >NUL && powershell -NoProfile -ExecutionPolicy Bypass -File "%TEMP%\%~0n.ps1" %*
@set "ec=%ERRORLEVEL%" & del "%TEMP%\%~0n.ps1"
@exit /b %ec%
#>

# Paste arbitrary PowerShell code here.
# In this example, all arguments are echoed.
'Args:'
$Args | % { 'arg #{0}: [{1}]' -f ++$i, $_ }

Note: A temporary *.ps1file that is cleaned up afterwards is created in the %TEMP%folder; doing so greatly simplifies passing arguments through (reasonably) robustly, simply by using %*

注意:*.ps1%TEMP%文件夹中创建了一个后来清理的临时文件;这样做可以大大简化(合理地)稳健地传递参数,只需使用%*

  • Line <# ::is a hybrid line that PowerShell sees as the start of a comment block, but cmd.exeignores, a technique borrowed from npocmaka's answer.

  • The batch-file commands that start with @are therefore ignored by PowerShell, but executed by cmd.exe; since the last @-prefixed line ends with exit /b, which exits the batch file right there, cmd.exeignoresthe rest of the file, which is therefore free to contain non-batch-file code, i.e., PowerShell code.

  • The #>line ends the PowerShell comment block that encloses the batch-file code.

  • Because the file as a whole is therefore a valid PowerShell file, no findstrtrickery is needed to extract the PowerShell code; however, because PowerShell only executes scripts that have filename extension .ps1, a (temporary) copyof the batch file must be created; %TEMP%\%~0n.ps1creates the temporary copy in the %TEMP%folder named for the batch file (%~0n), but with extension .ps1instead; the temporarily file is automatically removed on completion.

  • Note that 3 separate linesof cmd.exestatements are needed in order to pass the PowerShell command's exit code through.
    (Using setlocal enabledelayedexpansionhypothetically allows doing it as a singleline, but that can result in unwanted interpretation of !chars. in arguments.)

  • Line<# ::是一条混合线,PowerShell 将其视为注释块的开始,但cmd.exe忽略了这一技术,该技术从npocmaka 的回答中借用。

  • @因此,以 开头的批处理文件命令会被 PowerShell 忽略,但由cmd.exe; 由于最后一个带@前缀的行以 结束exit /b,它在那里退出批处理文件,cmd.exe忽略文件的其余部分,因此可以自由包含非批处理文件代码,即 PowerShell 代码。

  • #>行结束包含批处理文件代码的 PowerShell 注释块。

  • 因为整个文件是一个有效的 PowerShell 文件,所以findstr提取 PowerShell 代码不需要任何技巧;但是,由于 PowerShell 只执行具有文件扩展名的脚本,因此必须创建批处理文件.ps1的(临时)副本%TEMP%\%~0n.ps1%TEMP%以批处理文件 ( %~0n)命名的文件夹中创建临时副本,但使用扩展名.ps1;临时文件在完成时自动删除。

  • 请注意,需要 3单独的cmd.exe语句来传递 PowerShell 命令的退出代码。
    (使用setlocal enabledelayedexpansion假设允许做它作为一个单一的线,但可能导致不必要的解释!中的参数字符。)



To demonstrate the robustness of the argument passing:

为了证明参数传递稳健性

Assuming the code above has been saved as sample.cmd, invoking it as:

假设上面的代码已保存为sample.cmd,调用它为:

sample.cmd "val. w/ spaces & special chars. (\|<>'), on %OS%" 666 "Lisa \"Left Eye\" Lopez"

yields something like the following:

产生如下内容:

Args:
arg #1: [val. w/ spaces & special chars. (\|<>'), on Windows_NT]
arg #2: [666]
arg #3: [Lisa "Left Eye" Lopez]

Note how embedded "chars. were passed as \".
However, there are edge casesrelated to embedded "chars.:

注意如何嵌入"字符。被作为\".
但是,存在与嵌入字符相关的边缘情况"

:: # 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"

These difficulties are owed to cmd.exe's flawed argument parsing, and ultimately it is pointless to try to hidethese flaws, as flabdablet points out in his excellent answer.

这些困难cmd.exe归咎于有缺陷的参数解析,最终试图隐藏这些缺陷是没有意义的,正如flabdablet 在他的优秀回答中指出的那样

As he explains, escaping the following cmd.exemetacharacters with ^^^(sic) inside the \"...\"sequence solves the problem:

正如他所解释的,序列中使用(sic)转义以下cmd.exe元字符可以解决问题:^^^\"...\"

& | < >

Using the example above:

使用上面的例子:

:: # OK: cmd.exe metachars. inside \"...\" are ^^^-escaped.
sample.cmd "A \"rock ^^^& roll\" life style"

回答by Kyle

This supports arguments unlike the solution posted by Carlos and doesn't break multi-line commands or the use of paramlike the solution posted by Jay. Only downside is that this solution creates a temporary file. For my use case that is acceptable.

这支持与 Carlos 发布的解决方案不同的参数,并且不会破坏多行命令或使用param类似 Jay 发布的解决方案。唯一的缺点是此解决方案会创建一个临时文件。对于我的用例,这是可以接受的。

@@echo off
@@findstr/v "^@@.*" "%~f0" > "%~f0.ps1" & powershell -ExecutionPolicy ByPass "%~f0.ps1" %* & del "%~f0.ps1" & goto:eof

回答by John Weldon

Without fully understanding your question, my suggestion would be something like:

在没有完全理解你的问题的情况下,我的建议是这样的:

@echo off
set MYSCRIPT="some cool powershell code"
powershell -c %MYSCRIPT%

or better yet

或者更好

@echo off
set MYSCRIPTPATH=c:\work\bin\powershellscript.ps1
powershell %MYSCRIPTPATH%

回答by flabdablet

My current preference for this task is a polyglot header that works much the same way as mklement0's first solution:

我目前对这项任务的偏好是多语言标题,其工作方式与mklement0 的第一个解决方案大致相同:

<#  :cmd header for PowerShell script
@   set dir=%~dp0
@   set ps1="%TMP%\%~n0-%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.ps1"
@   copy /b /y "%~f0" %ps1% >nul
@   powershell -NoProfile -ExecutionPolicy Bypass -File %ps1% %*
@   del /f %ps1%
@   goto :eof
#>

# Paste arbitrary PowerShell code here.
# In this example, all arguments are echoed.
$Args | % { 'arg #{0}: [{1}]' -f ++$i, $_ }

I prefer to lay the cmd header out as multiple lines with a single command on each one, for a number of reasons. First, I think it's easier to see what's going on: the command lines are short enough not to run off the right of my edit windows, and the column of punctuation on the left marks it visually as the header block that the horribly abused label on the first line says it is. Second, the deland gotocommands are on their own lines, so they will still run even if something really funky gets passed as a script argument.

出于多种原因,我更喜欢将 cmd 标头放置为多行,每行一个命令。首先,我认为更容易看到发生了什么:命令行足够短,不会跑出我的编辑窗口的右侧,左侧的标点列在视觉上将其标记为标题块,上面的标签被严重滥用第一行说是。其次,delandgoto命令在它们自己的行上,因此即使某些非常时髦的东西作为脚本参数传递,它们仍然会运行。

I have come to prefer solutions that make a temporary .ps1file to those that rely on Invoke-Expression, purely because PowerShell's inscrutable error messages will then at least include meaningful line numbers.

我开始更喜欢制作临时.ps1文件的解决方案,而不是那些依赖Invoke-Expression.

The time it takes to make the temp file is usually completely swamped by the time it takes PowerShell itself to lumber into action, and 128 bits worth of %RANDOM%embedded in the temp file's name pretty much guarantees that multiple concurrent scripts won't ever stomp each other's temp files. The only real downside to the temp file approach is possible loss of information about the directory the original cmd script was invoked from, which is the rationale for the direnvironment variable created on the second line.

制作临时文件所需的时间通常完全被 PowerShell 本身所花费的时间淹没,%RANDOM%临时文件名称中嵌入的128 位值几乎可以保证多个并发脚本永远不会相互干扰临时文件。临时文件方法的唯一真正缺点是可能丢失有关调用原始 cmd 脚本的目录的信息,这是dir在第二行创建环境变量的基本原理。

Obviously it would be far less annoying for PowerShell not to be so anal about the filename extensions it will accept on script files, but you go to war with the shell you have, not the shell you wish you had.

显然,对于 PowerShell 来说,不要对它在脚本文件上接受的文件扩展名如此挑剔会少得多,但是你会与你拥有的 shell 交战,而不是你希望拥有的 shell。

Speaking of which: as mklement0 observes,

说到这里:正如 mklement0 所观察到的,

# BREAKS, due to the `&` inside \"...\"
sample.cmd "A \"rock & roll\" life style"

This does indeed break, due to cmd.exe's completely worthless argument parsing. I've generally found that the less work I do to try to hidecmd's many limitations, the fewer unanticipated bugs I cause myself down the line (I am sure I could come up with arguments containing parentheses that would break mklement0's otherwise impeccable ampersand escaping logic, for example). Less painful, in my view, just to bite the bullet and use something like

由于cmd.exe完全没有价值的参数解析,这确实会中断。我通常发现,我尝试隐藏cmd 的许多限制所做的工作越少,我给自己造成的意外错误就越少(我确信我可以提出包含括号的参数,这些参数会破坏 mklement0 否则无可挑剔的&符号转义逻辑, 例如)。在我看来,不那么痛苦,只是硬着头皮使用类似的东西

sample.cmd "A \"rock ^^^& roll\" life style"

The first and third ^escapes get eaten when that command line is initially parsed; the second one survives to escape the &embedded in the command line passed to powershell.exe. Yes, this is ugly. Yes, it does make it harder to pretend that cmd.exeisn't what gets first crack at the script. Don't worry about it. Document it if it matters.

^当最初解析该命令行时,第一个和第三个转义会被吃掉;第二个幸存下来以逃避&传递给命令行中的嵌入powershell.exe。是的,这很丑陋。是的,它确实让人更难假装这cmd.exe不是脚本的第一个破解。别担心。如果重要,请记录下来。

In most real-world applications, the &issue is moot anyway. Most of what's going to get passed as arguments to a script like this will be pathnames that arrive via drag and drop. Windows will quote those, which is enough to protect spaces and ampersands and in fact anything other than quotes, which aren't allowed in Windows pathnames anyway.

在大多数实际应用中,这个&问题无论如何都没有实际意义。大多数将作为参数传递给这样的脚本的内容将是通过拖放到达的路径名。Windows 将引用这些内容,这足以保护空格和与号,实际上除了引号之外的任何内容,无论如何都不允许在 Windows 路径名中使用。

Don't even get me started on Vinyl LP's, 12"turning up in a CSV file.

甚至不要让我开始Vinyl LP's, 12"出现在 CSV 文件中。

回答by Jean-Fran?ois Larvtheitroade

Another sample batch+PowerShell script... It's simpler than the other proposed solution, and has characteristics that none of them can match:

另一个示例批处理 + PowerShell 脚本......它比其他建议的解决方案更简单,并且具有它们都无法匹配的特性:

  • No creation of a temporary file => Better performance, and no risk of overwriting anything.
  • No special prefixing of the batch code. This is just normal batch. And same thing for the PowerShell code.
  • Passes all batch arguments to PowerShell correctly, even quoted strings with tricky characters like ! % < > ' $
  • Double quotes can be passed by doubling them.
  • Standard input is usable in PowerShell. (Contrary to all versions that pipe the batch itself to PowerShell.)
  • 不创建临时文件 => 更好的性能,并且没有覆盖任何内容的风险。
  • 批次代码没有特殊前缀。这只是普通批次。PowerShell 代码也是如此。
  • 将所有批处理参数正确传递给 PowerShell,甚至是带有棘手字符的引号字符串,例如 ! % < > ' $
  • 双引号可以通过加倍传递。
  • 标准输入可在 PowerShell 中使用。(与将批处理本身通过​​管道传输到 PowerShell 的所有版本相反。)

This sample displays the language transitions, and the PowerShell side displays the list of arguments it received from the batch side.

此示例显示语言转换,PowerShell 端显示它从批处理端收到的参数列表。

<# :# PowerShell comment protecting the Batch section
@echo off
:# Disabling argument expansion avoids issues with ! in arguments.
setlocal EnableExtensions DisableDelayedExpansion

:# Prepare the batch arguments, so that PowerShell parses them correctly
set ARGS=%*
if defined ARGS set ARGS=%ARGS:"=\"%
if defined ARGS set ARGS=%ARGS:'=''%

:# The ^ before the first " ensures that the Batch parser does not enter quoted mode
:# there, but that it enters and exits quoted mode for every subsequent pair of ".
:# This in turn protects the possible special chars & | < > within quoted arguments.
:# Then the \ before each pair of " ensures that PowerShell's C command line parser 
:# considers these pairs as part of the first and only argument following -c.
:# Cherry on the cake, it's possible to pass a " to PS by entering two "" in the bat args.
echo In Batch
PowerShell -c ^"Invoke-Expression ('^& {' + [io.file]::ReadAllText(\"%~f0\") + '} %ARGS%')"
echo Back in Batch. PowerShell exit code = %ERRORLEVEL%
exit /b

###############################################################################
End of the PS comment around the Batch section; Begin the PowerShell section #>

echo "In PowerShell"
$Args | % { "PowerShell Args[{0}] = '$_'" -f $i++ }
exit 0

Note that I use :# for batch comments, instead of :: as most other people do, as this actually makes them look like PowerShell comments. (Or like most other scripting languages comments actually.)

请注意,我使用 :# 进行批注释,而不是像大多数其他人那样使用 ::,因为这实际上使它们看起来像 PowerShell 注释。(或者实际上就像大多数其他脚本语言注释一样。)

回答by Xanius

I like Jean-Fran?ois Larvtheitroade'ssolution very much, especially for his handling of Arguments and passing them to the powershell-script diredtly (+1 added).

我非常喜欢Jean-Fran?ois Larvtheitroade 的解决方案,尤其是他处理参数并将它们直接传递给 powershell 脚本(+1 添加)。

But it has one flaw. AS I do npt have the reputatioin to comment, I post the correction as a new solution.

但它有一个缺陷。由于我确实 npt 有评论的声誉,因此我将更正作为新的解决方案发布。

The script name as argument for Invoke-Expression in double-quotes will not work when the script-name contains a $-character, as this will be evaluated before the file contents is loaded. The simplest remedy is to replace the double quotes:

当脚本名称包含 - 字符时$,作为双引号中 Invoke-Expression 参数的脚本名称将不起作用,因为这将在加载文件内容之前进行评估。最简单的补救方法是替换双引号:

PowerShell -c ^"Invoke-Expression ('^& {' + [io.file]::ReadAllText('%~f0') + '} %ARGS%')"

Personally, I rather prefer using get-contentwith the -rawoption, as to methis is more powershell'ish:

就我个人而言,我更喜欢使用get-content-raw选项,对我来说这更强大:

PowerShell -c ^"Invoke-Expression ('^& {' + (get-content -raw '%~f0') + '} %ARGS%')"

But that is, of course just my personal opinion. ReadAllText worksjust perfectly.

但这当然只是我个人的意见。ReadAllText works简直完美。

For completeness, the corrected script:

为完整起见,更正后的脚本:

<# :# PowerShell comment protecting the Batch section
@echo off
:# Disabling argument expansion avoids issues with ! in arguments.
setlocal EnableExtensions DisableDelayedExpansion

:# Prepare the batch arguments, so that PowerShell parses them correctly
set ARGS=%*
if defined ARGS set ARGS=%ARGS:"=\"%
if defined ARGS set ARGS=%ARGS:'=''%

:# The ^ before the first " ensures that the Batch parser does not enter quoted mode
:# there, but that it enters and exits quoted mode for every subsequent pair of ".
:# This in turn protects the possible special chars & | < > within quoted arguments.
:# Then the \ before each pair of " ensures that PowerShell's C command line parser 
:# considers these pairs as part of the first and only argument following -c.
:# Cherry on the cake, it's possible to pass a " to PS by entering two "" in the bat args.
echo In Batch
PowerShell -c ^"Invoke-Expression ('^& {' + (get-content -raw '%~f0') + '} %ARGS%')"
echo Back in Batch. PowerShell exit code = %ERRORLEVEL%
exit /b

###############################################################################
End of the PS comment around the Batch section; Begin the PowerShell section #>

echo "In PowerShell"
$Args | % { "PowerShell Args[{0}] = '$_'" -f $i++ }
exit 0