How to run a PowerShell script within a Windows ba

2019-01-08 19:00发布

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

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

  • Embedding SQL in a batch script using sqlcmd and 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.

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.

One possible solution I've considered is to echo out the PowerShell script, and then run it. A good reason to not do 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

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 World results in two echos on separate lines
  • %0 will give you the script that's currently running

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.

9条回答
我想做一个坏孩纸
2楼-- · 2019-01-08 19:45

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

@@echo off
@@findstr/v "^@@.*" "%~f0" > "%~f0.ps1" & powershell -ExecutionPolicy ByPass "%~f0.ps1" %* & del "%~f0.ps1" & goto:eof
查看更多
霸刀☆藐视天下
3楼-- · 2019-01-08 19:47

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.

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 *.ps1 file that is cleaned up afterwards is created in the %TEMP% folder; doing so greatly simplifies passing arguments through (reasonably) robustly, simply by using %*

  • Line <# :: is a hybrid line that PowerShell sees as the start of a comment block, but cmd.exe ignores, 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.exe ignores the 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 findstr trickery is needed to extract the PowerShell code; however, because PowerShell only executes scripts that have filename extension .ps1, a (temporary) copy of the batch file must be created; %TEMP%\%~0n.ps1 creates the temporary copy in the %TEMP% folder named for the batch file (%~0n), but with extension .ps1 instead; the temporarily file is automatically removed on completion.

  • Note that 3 separate lines of cmd.exe statements are needed in order to pass the PowerShell command's exit code through.
    (Using setlocal enabledelayedexpansion hypothetically allows doing it as a single line, but that can result in unwanted interpretation of ! chars. in arguments.)


To demonstrate the robustness of the argument passing:

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

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 cases related 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 hide these flaws, as flabdablet points out in his excellent answer.

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

& | < >

Using the example above:

:: # OK: cmd.exe metachars. inside \"...\" are ^^^-escaped.
sample.cmd "A \"rock ^^^& roll\" life style"
查看更多
狗以群分
4楼-- · 2019-01-08 19:49

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

dosps.cmd:

@powershell -<%~f0&goto:eof
Write-Output "Hello World" 
Write-Output "Hello World again" 
查看更多
登录 后发表回答