我想把PowerShell脚本打包成一个批处理文件,最终使它在一个单独的分布式文件中对任何人都可执行。我的想法是用CMD命令开始一个文件,CMD命令读取文件本身,跳过前几行,把剩下的传输到powershell,然后让批处理文件结束。在Bash中,这将是一个简单的任务,只需简短的可读命令。但是你可以从众多的技巧中看出Windows在这方面已经遇到了很大的麻烦。它看起来是这样的:
@echo off
(for /f "skip=4 delims=" %%a in ('type %0') do @echo.%%a) |powershell -noprofile -noninteractive -
goto :EOF
---------- BEGIN POWERSHELL ----------
write-host "Hello PowerShell!"
if ($true)
{
write-host "TRUE"
}
write-host "Good bye."
问题是,PowerShell脚本没有完全执行,它在写入第一行后停止。我可以让它工作更多,这取决于脚本本身,但在这里找不到任何模式。
如果你将第2行的结果通过管道传输到一个文件或cat
进程(如果你安装了unix工具,Windows甚至不能自己完成这个操作),你可以看到文件的完整PowerShell部分,所以它不会被切断或其他什么。
复制该文件,删除前4行并将其重命名为 *.ps1,然后调用以下命令:
powershell -ExecutionPolicy unrestricted -File FILENAME.ps1
并且它会完全执行。所以它也不是PowerShell脚本内容。在这种情况下,是什么让powershell过早结束?
4条答案
按热度按时间5q4ezhmt1#
您可以使用注解块在powershell中隐藏脚本的批处理文件部分,然后按原样运行脚本,而不必使用批处理文件修改它:
lokaqttq2#
遗憾的是,将 * 脚本内容传送到
powershell.exe
/pwsh
,即通过 * stdin、-
提供PowerShell代码-无论是通过-Command
(-c
)(powershelle.exe
默认值)还是-File
(-f
)(pwsh
默认值)-都有严重的限制:它表现出伪交互行为,需要 * 两个 * 尾随换行符来终止语句,并且缺乏对参数传递的支持;参见GitHub issue #3223。if
语句与缺少额外换行符的组合(空行),这会导致脚本处理提前执行,因为该多行语句的结尾(包括所有后续语句)无法识别。增加的问题是for /f
* 删除空行 *,所以在}
结束后加1 * 不 * 起作用;虽然您可以使用 * non-empty,all-whitespace * 行(一个空格就可以了),它不会被删除,但是这种晦涩难懂的变通方法(每个多行语句之后都需要)不值得这么麻烦,所以最好避免使用基于stdin的方法。在Anon Coward's excellent trick for hiding the batch-file portion of the file from PowerShell的基础上,我将提供以下改进:
.ps1
的批处理文件的 * 副本 ,并传递给PowerShell CLI的-file
(-f
)参数:*-file
调用脚本保留了通常的PowerShell脚本体验,因为脚本报告自己的文件名和路径,而且可能更重要的是,它可靠地支持使用%*
传递的 * 参数.ps1
的文件作为-file
参数,因此会创建具有该扩展名的批处理文件的 * 副本 *。%TEMP%
中创建该副本和/或在调用PowerShell后自动删除该副本;参见this answer)。exit /b %ERRORLEVEL%
用于退出批处理文件,以确保PowerShell的退出代码通过。Invoke-Expression
的解决方案以支持 (相当健壮)参数传递**:这种方法避免了对扩展名为
.ps1
的批处理文件的(临时)副本的需要;一个小缺点是基于Invoke-Expression
的调用会使代码报告 * 空字符串 * 作为脚本自己的文件路径(通过自动变量$PSCommandPath
)和目录(通过$PSScriptRoot
)。2然而,您可以使用$batchFilePath = ([Environment]::CommandLine -split '""')[1]
来获得批处理文件的完整路径。%__args%
/$env:__args
用于将所有参数(%*
)传递到PowerShell,在PowerShell中,其值合并到传递给Invoke-Expression
的字符串中;注意文件内容如何被封装在. { ... }
中,即经由script block的调用,使得代码支持接收自变量。"
字符的参数,例如"3\" of snow"
;对于包含不平衡的'
字符数的无引号参数,同上。(例如,6'
)$
前缀的子字符串的情况下(例如"amount: $3"
);此外,'
字符在PowerShell中具有语法功能(例如,'aha'
将变成逐字的aha
,其中引号被移除),并且```字符被解释为 * escape * 字符。%~f0
是如何包含在\"...\"
中的(PowerShell最终将其视为"..."
);使用"
而不是'
加引号稍微更健壮,因为允许文件路径包含'
字符本身,但不允许包含"
。Get-Content
的-Raw
交换机不受支持,请更换Get-Content -Raw -LiteralPath ""%~f0""
与Get-Content LiteralPath ""%~f0"" | Out-String
iugsix8n3#
这种方法与Anon Coward的方法非常相似,但是不依赖于
-Raw
选项,该选项是在powershell-3.0中引入的:为了更好地阅读,我将长PowerShell命令行拆分为多个,但这不是必需的:
thtygnil4#
谢谢你的回答!仅供参考,这是我从提供的来源整理出来的:
批处理部分已经有一个很长的行,所以我想我只是把所有的样板代码塞在一行中。
在这次编辑中,基于mklement 0编辑后的答案,命令行参数处理相当完整。
返回代码(errorlevel)仅在批处理文件使用
call
执行时可见,如下所示:如果没有
call
,返回值总是0,并且总是显示“OK”。这是CMD和批处理文件的一个经常被遗忘的限制。