How do you put PowerShell code into a batch/cmd file without having to also have a separate .ps1 file? (If you can have an external .ps1 file – you just invoke powershell.exe and supply the path to the script file as parameter.)
I got this question recently from one of our field guys and thought I would blog about the trick publicly.
The problem is that PowerShell syntax can obviously have elements that .bat files cannot stand, and that you cannot pass multiline script as powershell.exe parameter.
There are actually a couple of ways to do so:
1. Encode the script:
As Bruce points here PowerShell has the -EncodedCommand
parameter, which lets you pass any PowerShell code as base-64-encoded string.
So if you have some sort of script, like this:
#iterate numbers 1 through 10 1..10 | foreach-object { # just output them "Current output:" $_ }
You simply (in PowerShell command-line or script) put it in curcly brackets and assign (as scriptblock) to a variable:
$code = { #iterate numbers 1 through 10 1..10 | foreach-object { # just output them "Current output:" $_ } }
Then use this PowerShell command to get the encoded version:
[convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($code))
Then copy/paste the output of the command to your batch file:
powershell.exe -EncodedCommand DQAKAAkAIwBpAHQAZQByAGEAdABlACAAbgB1AG0AYgBlAHIAcwAgADEAIAB0AGgAcgBvAHUAZwBoACAAMQAwAA0ACgAJADEALgAuADEAMAAgAHwAIABmAG8AcgBlAGEAYwBoAC0AbwBiAGoAZQBjAHQAIAB7AA0ACgAJACMAIABqAHUAcwB0ACAAbwB1AHQAcAB1AHQAIAB0AG
gAZQBtAA0ACgAJACIAQwB1AHIAcgBlAG4AdAAgAG8AdQB0AHAAdQB0ADoAIgANAAoACQAkAF8ADQAKAAkAfQANAAoA
2. Keep the code as PowerShell but turn it to a string:
If the first approach for whatever reason does not work for you (e.g. you care about readability), you can try to flatten the script and pass it as a string:
- Take the PowerShell script.
- Remove all the comments ( everything that starts with #).
- Put ; at the end of each line.
- Remove all line breaks.
- Supply the string you get as the -command parameter for powershell.exe.
The reason for all of this is that powershell.exe (the executable which allows you to run any PowerShell code allows you to either start an external .ps1 script file (which often creates additional complexity of having to maintain and ship 2 files) or execute a single line of PowerShell code as the -command parameter. Hence the requirement to flatten the script and turn something like this:
#iterate numbers 1 through 10 1..10 | foreach-object { # just output them "Current output:" $_ }
into:
powershell.exe -command '1..10 | foreach-object { "Current output:"; $_; }'
See also this blog post by MoW on making PowerShell pass its exit code to command files.
Tags: KB, Knowledge Base, Known Issues, PowerShell
As far as I can see, the double quotes will get the command interpreter confused, so you will have to escape them – as in
powershell.exe -command “1..10 | foreach-object { \”Current ou
tput:\”; $_; }”
If you don’t escape them you get the message:
The term ‘Current’ is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At line:1 char:33
+ 1..10 | foreach-object { Current <<<< output:; $_; }
You also missed off the closing double quote, but that didn’t seem to make a difference.
Thanks for the post though, it could be useful for short scripts.
I am invoking a powershell script from batch file.
I want to return parameter from Powershell script (.PS1) to Batch file, how to do it?
and How to assign the return parameter from powershell script to some variable in batch file?
Thanks
Rajeev
Rajeev,
I believe you can just supply the code in the exit command – e.g.:
exit 1
If this does not help, please ask at the forum at powergui.org – there are a lot of smart guys there and they should be able to help.
Dmitry
powershell.exe -command “1..10 | foreach-object { ‘Current output:’; $_; }”
One way to completely avoid the quoting problem is to use the -encoded parameter on powershell.exe. This takes a Base64 encoded string, decodes it and then executes it. For example, we’ll put some code in a script block (since it will be syntax checked) instead of a simple string:
PS (41) > $code = {
>> #iterate numbers 1 through 10
>> 1..10 | foreach-object {
>> # just output them
>> “Current output:”
>> $_
>> }
>>
>> }
>>
Now encode the command:
PS (42) > $encoded = [convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($code))
The result of this looks like:
PS (43) > $encoded
MQAuAC4AMQAwACAAfAAgAGYAbwByAGUAYQBjAGgALQBvAGIAagBlAGMAdAAgAHsACgAjACAAagB1AHMAdAAgAG8AdQB0AHAAdQB0ACAAdABoAGUAbQAKACI
AQwB1AHIAcgBlAG4AdAAgAG8AdQB0AHAAdQB0ADoAIgAKACQAXwAKAH0ACgA=
Then run it:
(CXT: win7) (STA-ISS) (44) > powershell -encoded MQAuAC4AMQAwACAAfAAgAGYAbwByAGUAYQBjAGgALQBvAGIAagBlAGMAdAAgAHsACgAjACA
AagB1AHMAdAAgAG8AdQB0AHAAdQB0ACAAdABoAGUAbQAKACIAQwB1AHIAcgBlAG4AdAAgAG8AdQB0AHAAdQB0ADoAIgAKACQAXwAKAH0ACgA=
BTW – PowerShell has built-in support for this so when you call powershell from powershell with a scriptblock, the script block is automatically encoded and passed to the child process:
(CXT: win7) (STA-ISS) (46) > powershell { “Hi there. Today is ” + (get-date).DayOfWeek }
Hi there. Today is Saturday
-bruce
——————–
Principal Developer, Windows PowerShell Team
Microsoft
Hi Could you please help me writing this powershell code into a .bat file.
POWERSHELL CODE
param([string]$alertID,[string[]]$Recipients)
$error.clear()
$erroractionpreference = “SilentlyContinue”
$thisScript = $myInvocation.MyCommand.Path
$scriptRoot = Split-Path(Resolve-Path $thisScript)
$errorLogFile = Join-Path $scriptRoot “error.log”
if (Test-Path $errorLogFile) {Remove-Item $errorLogFile -Force}
function getResStateName($resStateNumber)
{
switch($resStateNumber)
{
“0” { $resStateName = “New” }
#”85″ { $resStateName = “Notified” }
“255” { $resStateName = “Closed” }
}
$resStateName
}
———————
i tried below but it errored out stating ‘param’ is not recognized as an internal or external command,
operable program or batch file.
——————————–
param([string]$alertID,[string[]]$Recipients); $error.clear(); $erroractionpreference = “SilentlyContinue”; $thisScript = $myInvocation.MyCommand.Path; $scriptRoot = Split-Path(Resolve-Path $thisScript);$errorLogFile = Join-Path $scriptRoot “error.log”;if (Test-Path $errorLogFile) {Remove-Item $errorLogFile -Force;}$kbxmlfile =”E:\Scripts\new2.xml”; function getResStateName($resStateNumber){ switch($resStateNumber) { “0” { $resStateName = “New” ;} “255” { $resStateName = “Closed” ;} }
Manish,
In general, use the -EncodedCommand approach recommended by Bruce above.
However, your code seems to expect input parameters ($alertID and $recipients) which makes is tricky… Do you actually need to pass these as parameters? Do the values come from the bat file?
One thing you could try to do is: save the script as ps1 file and then invoke:
powershell.exe -Command “& c:\myscript.ps1 123 ‘foo'”
Dmitry
;@echo off & Title Demo Batch_PS
;Findstr -rbv ; %0 | powershell -c –
;goto:sCode
Function Get-MyFavoriteBeep
{
$s = “262_500″,”393_500″,”350_100″,”330_100″,”294_100″,”525_500”
$s += “393_500″,”350_100″,”330_100″,”294_100″,”525_500″,”393_500”
$s += “350_200″,”330_200″,”350_200″,”294_400”
$s |% {
[console]::beep($_.split(“_”)[0],$_.split(“_”)[1])
}
}
Get-MyFavoriteBeep
;:sCode
;echo done
;pause & goto :eof
Very neat. This ‘;’ prefix does the job nicely. It’s a shame Windows doesn’t support #! though.
on dirai qu’il y a un problème avec les guillemets, ils sont males interpréter par la balise “code”
voici un autre exemple:
;@echo off & Title Demo Batch_PS
;Findstr -rbv ; %0 | powershell -c –
;goto:sCode
1..9 | % { write-host $env:username -fore $_ }
;:sCode
;echo done
;pause & goto :eof
Walid,
You can either use single quotes inside double quotes:
“This string has substrings ‘a’ and ‘b’ and gets parsed fine”
Or -EncodedCommand as explained above.
Dmitry
hi Dmitry,
another variant:
@echo off
::::::::::::::::::::::::::::::::::::
For /f “delims=:” %%a In (‘
findstr /Bn “” %0
‘) do Set /ALine=%%a
more +%Line% %0 | powershell -c –
::::::::::::::::::::::::::::::::::::
:: ici suite du code Batch
dir
pause & exit /b
::::::::::::::::::::::::::::::
Function Get-Test {
param()
Begin{
Write-Warning “Debut du traitement”
}
Process{
write-verbose “ici n’importe quel traitement”
gps power*,cmd*
}
End{
Write-warning “Fin du traitement”
}
}
Get-Test
exit
Dmitry,
sorry for my english,
les codent que j’ai mis dans ton blog sont tronqués, il y a un problème avec la balise “code” voici le lien de mon blog avec les scripts en question
http://walid-toumi.blogspot.com/
merci
Thanks a lot Dude. The script sample saved a lot of time…….
Thanks for this.. But how can I schedule this batch file in task scheduler? Do I have to add arguments?
I’ve tried but its not working
See if this helps: https://dmitrysotnikov.wordpress.com/2011/02/03/how-to-schedule-a-powershell-script/
Hi Dmitry!
I’m making a batch file who is supposed to run av powershell script file, but it won’t work.
As you said above: “If you can have an external .ps1 file – you just invoke powershell.exe and supply the path to the script file as parameter.”
Well.. i can’t make it work.
Here is my batch-file code:
———————–
@ECHO off
echo test!
C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe C:\Scripts\test.ps1
———————–
I’m running the batchfile from java which do work, but the batch file never runs the .ps1 file. Any ideas where i’m failling?
Regards
Håkon Lund
Instead try:
C:>cd Scripts
powershell -command ./test.ps1
Best of luck
George Gyulatyan
hi. I am running a powershell.exe line within a batch file. How can i pass named variables obtained/set during the traditional cmd/batch section of the batch file into my powershell.exe line?
e.g.
earlier in the batch file,
set DB = myDBname
is executed (this value needs to be set depending on the scenario and cannot be trivially set within the powershell.exe line)
I then want to pass this variable to be used in the -database variable for invoke-sqlcmd
thanks
Hey, check my one:
———-
@powershell -command “(Get-Content \”%0\”) | select -skip 1 ” | powershell -c – & goto :eof
Write-Host “put your powershell code here”
———-
Update to the reply of den PO:
@setlocal
@set command=”(Get-Content \”%~f0\”) | select -skip 4″
@powershell -NoProfile -ExecutionPolicy Bypass -command %command% | powershell -c –
@goto :eof
Write-Host “Hello world”;
I was wondering if I could get a little help with the attempt at zipping a few files in a directory. PS scripts have been blocked, running them with a batch file seems to work but I can’t figure out what I am doing wrong when I covert the following:
$srcdir = “C:\temp222”
$zipFilename = “APS.zip”
$zipFilepath = “C:\temp222”
$zipFile = “$zipFilepath$zipFilename”
if(-not (test-path($zipFile))) {
set-content $zipFile (“PK” + [char]5 + [char]6 + (“$([char]0)” * 18))
(dir $zipFile).IsReadOnly = $false
}
$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($zipFile)
$files = Get-ChildItem -Path $srcdir | where{! $_.PSIsContainer}
foreach($file in $files) {
$zipPackage.CopyHere($file.FullName)
while($zipPackage.Items().Item($file.name) -eq $null){
Start-sleep -seconds 1
}
}
+1