How to run a PowerShell script within a Windows batch file
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 commandsecho 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.
This one only passes the right lines to PowerShell:
dosps2.cmd
:
@findstr/v "^@f.*&" "%~f0"|powershell -&goto:eof
Write-Output "Hello World"
Write-Output "Hello some@com & again"
The regular expression excludes the lines starting with @f
and including an &
and passes everything else to PowerShell.
C:\tmp>dosps2
Hello World
Hello some@com & again
It sounds like you're looking for what is sometimes called a "polyglot script". For 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.
Here the 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.
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_ARGS
are command line arguments first set as variable in the batch part.
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.
The only thing left is to find a proper way for PowerShell to read and execute %~f0
which is the full path to the script executed by cmd.exe.
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"