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

25
如何将PowerShell脚本嵌入到与Windows批处理脚本相同的文件中?
我知道在其他情况下这种事情是可能的:
- 使用`sqlcmd`和文件开头的精心安排的goto和注释将SQL嵌入批处理脚本中 - 在*nix环境中,将希望运行脚本的程序名称作为脚本第一行的注释,例如`#!/usr/local/bin/python`。
也许没有办法做到这一点 - 如果是这样,我将不得不从启动脚本中调用单独的PowerShell脚本。
我考虑过的一个可能的解决方案是回显PowerShell脚本,然后运行它。不这样做的一个很好的理由是,尝试这样做的部分原因是使用PowerShell环境的优势,而不需要例如转义字符的麻烦。
我有一些不寻常的限制,并希望找到一个优雅的解决方案。我怀疑这个问题可能会引起各种回应:“为什么你不尝试解决不同的问题。”可以说这些是我的限制,对此感到抱歉。
有什么想法吗?是否有适当的巧妙注释和转义字符组合,可以使我实现这一点?
关于如何实现这一点的一些想法:
  • 行末的克拉符号^表示续行,类似于Visual Basic中的下划线
  • 和号&通常用于分隔命令,echo Hello & echo World会在两行上分别输出两个echo
  • %0将给出当前运行的脚本

所以如果我能让它正常工作,就像这样:

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

除非...

  • 它不起作用...因为
  • 文件的扩展名不符合PowerShell的喜好:Windows PowerShell控制台文件"insideout.bat"的扩展名不是psc1。Windows PowerShell控制台文件扩展名必须是psc1。
  • CMD对此情况也不太满意 - 尽管它会在'#'上绊倒,但它不被识别为内部或外部命令、可操作程序或批处理文件。
18个回答

1

如果我没有完全理解你的问题,我的建议是这样的:

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

或者更好的是

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

非常感谢 - 不过这不是我想要的:像你回答的第一部分一样,我想将PowerShell脚本直接合并到批处理文件中,但是通过传递脚本本身给PowerShell来运行。也许我会更新我的问题,并展示一个类似的示例。 - Don Vince

1

您可以在Powershell脚本之前添加三行,仅使用块注释,然后将其保存为批处理文件。然后,您就可以有一个批处理文件来运行Powershell脚本。示例:

psscript.bat

@echo off
@powershell -command "(Get-Content -Encoding UTF8 '%0' | select-string -pattern '^[^@]')" | @powershell -NoProfile -ExecutionPolicy ByPass
@goto:eof
<# Must use block comment; Powershell script starts below #>
while($True) {
    Write-Host "wait for 3s"
    Start-Sleep -Seconds 3
}

还不错,如果 PowerShell 脚本没有被输出就更好了。 - Stefano

1
将一些想法汇集在一起。
<# :
@powershell -<%~f0&goto:eof
#>

Write-Output "Hello World" 
Write-Output "Hello World again" 

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community
如果其他人尝试这个程序,请注意两件重要的事情:如果您删除空行,它将无法正常工作。在结尾处使用暂停会显示消息,但实际上不会暂停。 - Stefano

1
@powershell -noninteractive "& ([Scriptblock]::Create((gc '%~df0' | select -Skip 1 | Out-String))) %*" & goto :eof

0

我的提供如下:

  • 使用certutil.exe对输入文件进行哈希处理,以形成临时文件名
  • 使用more.exe剥离头部,并在临时文件中创建文件
  • 将所有的-arg xxx样式参数传递给powershell
  • 保留ps1文件在临时文件夹中,以便您查看。

示例用法


.\runner.bat
.\runner.bat -scope town
.\runner.bat -scope "whole universe"

runner.bat

@echo off & (For /F Delims^= %%a In ('CertUtil -HashFile %0 SHA1^|FindStr /VRC:"[^a-f 0-9]"') Do Set "PS1=%TEMP%\%%a.ps1" )
(if not exist %PS1% more +3 %0 > %PS1%) & (PowerShell.exe -ExecutionPolicy bypass -file %PS1% %* & goto :EOF)
@@@@@@[ PowerShell Starts Here ]@@@@@@

Param(
    [String]$scope = "world"
)
write-host "hello $scope"



0

这个获得最高票的findstr回答缺少我解决方案具备的以下功能:

  • Read-Host/pause现在可以工作
  • 参数传递(包括带引号的参数)
  • 返回代码传递
<# :
  @set batch_args=%*
  @powershell "iex ('$args = @(iex \"^& {`$args} $env:batch_args\");' + (cat -Raw '%~f0'))"
  @exit /b %ERRORLEVEL%
#>

Write-Host Your PowerShell code goes here. First arg: $args[0] -fore Green
Read-Host Waiting for user to press Enter
Exit 42

如果您不需要参数,可以直接执行 @powershell "iex (cat -Raw '%~f0')"


解释:

  • <# ... #> 是 PowerShell 中的注释块,因此会被跳过,但是 <# : 在批处理中被解释为空重定向,因此该注释只作为批处理代码运行
    • exit /b %ERRORLEVEL% 使其返回 PowerShell 的退出代码
  • PowerShell 只会运行具有 .ps1 结尾的文件,因此我们不直接使用 PowerShell 执行当前文件,而是调用 Invoke-Expression/iex 来执行它
    • 在批处理中,%~f0 是当前代码文件的名称
    • $args 通常是一个自动的 PowerShell 变量,包含命令行参数,但在这里,我们在加载的文件顶部放置了一行手动设置它的代码
  • 在批处理中,@ 防止命令在命令行上回显
  • 在批处理中,%* 指的是传递给脚本的所有参数。
    • 你在其他答案中看到的 & {$args} %* 片段创建了一个只返回其参数并在其后执行批处理参数的代码块
    • 在该片段中,带引号的参数不起作用,因为 PowerShell 是可疑的 enter image description here
    • 如果我执行 $args = (echo %*),那么如果没有传递参数,echo 会等待用户交互 :/
    • 因此,我的超级特殊解决方法是设置一个环境变量并使用 Invoke-Expression 进行两次解析,这解决了错误引号问题并避免了无参数的问题!
    • @ 确保我们始终得到一个数组, 即使我们没有任何参数
ScriptBlock 基于的答案存在一个问题,即 Write-Host 语句会立即显示,而 Write-Output/echo 基于的答案只有在块完成后才会显示,这对我的用例来说是致命的(使用 Read-Host 在出现错误时保持 cmd 窗口打开)。

0
好的,我承认我只贡献了1%,但我想为这个帖子做出一点贡献,因为我一次又一次地回到这里。这是上面所有片段的成熟结晶,有效地解决了我所知道的所有不同的错误。我构建了这个工具,以便在我们的组织中更轻松地启动PowerShell脚本。
<# PowerShell comment protecting the Batch section
@echo off

:# Clear the console to suppress any errors.
cls

:# Disabling argument expansion avoids issues with ! in arguments.
SetLocal EnableExtensions DisableDelayedExpansion

:# Checking for Administrator Privileges.
net session >nul 2>&1
IF NOT %ErrorLevel% == 0 (
    ECHO. Failure: Current permissions are inadequate.
    EXIT /B 1
)

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

:# Ensure FilePath is utilizing a lettered drive path.
SET "FilePath=%~f0"
IF "%FilePath:~0,2%" == "\\" PUSHD "%~dp0"
IF "%FilePath:~0,2%" == "\\" SET "FilePath=%CD%\%~nx0"
IF NOT "%FilePath:~0,2%" == "\\" CD "%~dp0"

:# Escape the file path for all possible invalid characters.
SET "FilePath=%FilePath:'=''%"
SET "FilePath=%FilePath:^=^^%"
SET "FilePath=%FilePath:[=`[%"
SET "FilePath=%FilePath:]=`]%"
SET "FilePath=%FilePath:&=^&%"

:# ============================================================================================================ #:
:# 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; Entering PowerShell.
"%WinDir%\System32\WindowsPowerShell\v1.0\powershell.exe" -NonInteractive -NoLogo -NoProfile -Command ^
    ^"Invoke-Expression ('^& {' + (get-content -raw '%FilePath%') + '} %ARGS%')"
ECHO Exited PowerShell; Back in BATCH.

pause
POPD
exit /b

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

0

一个修改过的版本 小红帽 的答案。 [ 因为 Stackoverflow 的编辑队列已满 :D ]

为了完整性:

  • 添加了缺失的有用的 PowerShell 参数
  • 返回 ErrorLevel 以改进错误处理。
  • 支持单引号和双引号参数
  • 消除了 这个讨厌的 bug
  • 确保与批处理和 PowerShell 语法高亮兼容,防止语法错误。

代码片段:

<# :
@PowerShell -ExecutionPolicy Bypass -NoLogo -NoProfile "& ([Scriptblock]::Create((Get-Content '%~df0' | Select-Object -Skip 4 | Out-String))) %*" & goto :eof
exit /b %errorlevel%
#>

完整演示脚本:
<# :
@PowerShell -ExecutionPolicy Bypass -NoLogo -NoProfile "& ([Scriptblock]::Create((Get-Content '%~df0' | Select-Object -Skip 4 | Out-String))) %*" & goto :eof
exit /b %errorlevel%
#>
Write-Output $PSVersionTable
Write-Output $args
$host.ui.RawUI.WindowTitle = "PowerShell Chimera"
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
Start-Sleep -Seconds 5
Exit 0x539

使用示例:

# demo.bat var1="1" var2='2' & echo %errorlevel%
# demo.bat "var1=1" 'var2=2' & echo %errorlevel%

输出:

Name                           Value
----                           -----
PSVersion                      5.1.19041.868
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.868
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
var1=1
var2=2
WINDOWS-PC\User
1337

旁注: 如果你对小红帽原始答案中使用的-NonInteractive参数不确定,我在下面引用了一个解释。

我认为你误解了-NonInteractive开关的用法;你仍然可以运行powershell -noninteractive并获得一个交互提示符。 NonInteractive开关是用于自动化脚本场景的,其中你不希望powershell向用户发送提示并等待响应。例如,在非交互式的PowerShell窗口中,如果你运行Get-Credential而没有任何参数,它将立即失败,而不是提示输入用户名和密码。非交互式不会作为一种安全机制。 更好的方法是保护你想要保护的内容,而不是用户可能使用的工具。

来源:jbsmith's回答问题“如何强制PowerShell不允许交互式命令窗口”


附注: 这个话题变成了一场PowerShell黑客竞赛:D


我不确定在这种情况下使用"ExecutionPolicy Bypass"是否有任何作用。一开始我也使用了它,直到意识到它是不必要的 :) "Scriptblock bypasses it anyway." - Red Riding Hood
好观点!我在脚本中加入了"-ExecutionPolicy Bypass"参数,以处理需要导入外部模块或执行脚本时遇到执行策略限制的情况。这个参数允许无缝导入,而不会触发与执行策略相关的任何错误。值得注意的是,在最新版本的PowerShell中,可能并不总是需要绕过参数。脚本块无论如何都会绕过执行策略。如果我的理解已经过时,请随时纠正我,因为这些知识是在病毒大流行之前的。 - Stefano
好观点!我在脚本中加入了"-ExecutionPolicy Bypass"参数,以处理需要导入外部模块或执行脚本时遇到执行策略限制的情况。这个参数允许无缝导入,而不会触发与执行策略相关的任何错误。值得注意的是,在最新版本的PowerShell中,可能并不需要绕过参数。脚本块无论如何都会绕过执行策略。如果我的理解已经过时,请随时纠正我,因为这个知识是在病毒大流行之前的。 - undefined

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