In olden days, I remember a trick we used to use to run Perl scripts within Windows cmd.exe as a simple invocation of a cmd file rather than having to run perl.exe with the script name, something like:
#rem = '
#perl -x -S %0 %*
#goto :eof
#rem ';
<insert Perl script here>
This used the rather neat trick of exploiting differences in how cmd.exe and perl.exe would handle the input script. Windows' cmd.exe would read the first three lines as three separate commands which would:
have a comment with no echo;
run perl with the same input file and arguments, without echo; and
goto the end of the file (exit) with no echo.
On the other hand, perl.exe would treat the first four lines as an assignment statement, then go on to execute the Perl script proper.
Now it's often a pain to run Powershell scripts from the cmd.exe command line since you have to use something like:
powershell -file go.ps1
I'm wondering if there's a way to encode both batch and powershell commands into a single cmd file, similar to the Perl trick, in such a way that it starts running under cmd.exe but switches to Powershell quickly after that.
If that were possible, you could run your script go.cmd simply by entering:
go
at the command line, rather than some convoluted invocation of powershell.
I know that you can ship separate cmd and ps1 files but that's in fact what I'm trying to get away from. I'm looking for a single file solution if possible.
I also know that you can base-64 encode your script or execute it as a string, provided you replace all newlines with semicolons. But that means the Powershell stuff is no longer easily editable or readable in the resulting file.
Fortunately or otherwise, Powershell knows exactly what to do with .cmd files: use CMD to run them.
#set $a=%0 && powershell -encodedcommand ZwBjACAAKAAoAGQAaQByACAAZQBuAHYAOgBgACQAYQApAC4AdgBhAGwAdQBlACAALQByAGUAcABsAGEAYwBlACAAJwAiACcALAAnACcAKQAgAHwAIABzAGUAbABlAGMAdAAgAC0AcwBrAGkAcAAgADIAIAB8ACAAcABvAHcAZQByAHMAaABlAGwAbAAgAC0A
#exit /b
"Hello from Powershell! You know it's me because cmd would never know that 2 + 2 = $(2 + 2)!"
The encoded command is
gc ((dir env:`$a).value -replace '\"','') | select -skip 2 | powershell -
Which fetches the contents of the script invoking itself, skips the stuff intended for CMD and, yes, runs Powershell on the rest.
Since we're piping commands through stdin and not having them in a proper script file there may be weirdness for more complicated scripts, doubly so if you start nesting these hybrids. I wouldn't trust this in production, but hey, it's something.
Related
I download a zip file from my cloud using a PowerShell command.
The command works correctly in PowerShell AND in the command line. However, if I insert the command from the command line into my batch script, only the html is downloaded.
Why does the command work correctly in the command line but not in the batch file?
Im out of ideas :D
powershell Invoke-WebRequest """https://sync.luckycloud.de/d/fb56e4a8239a4c6cac7a/files/?p=%2FValheimServer%20Buddelkiste%20Modpack%20v3.4%20-%20Standart.zip&dl=1""" -OutFile """C:\Users\Anonymos\Downloads\servermodpack.zip"""
Its works complete fine in cmd and load the ~40 Mb. But in Batch it loads only 9kb (its the Html)
In a batch file - as opposed to the interactive cmd.exe command prompt[1] - you need to to escape % chars. as %% in order to pass them through literally:
powershell -c "Invoke-WebRequest 'https://sync.luckycloud.de/d/fb56e4a8239a4c6cac7a/files/?p=%%2FValheimServer%%20Buddelkiste%%20Modpack%%20v3.4%%20-%%20Standart.zip&dl=1' -OutFile C:\Users\Anonymos\Downloads\servermodpack.zip"
Note:
I've used -c (-Command), the positionally implied Windows PowerShell CLI (powershell.exe) parameter explicitly for conceptual clarity (in PowerShell (Core) 7+, whose CLI is pwsh, the default is now -f (-File)).
Also, enclosing the entire command line to pass to PowerShell in "..." is generally preferable in order to prevent cmd.exe metacharacters other than % (such as &) from causing problems.
In case the command line requires use of embedded " characters (the solution above avoids this by using embedded '...' quoting), the safest way to escape them is to use "^"" (sic) with powershell.exe, and "" with pwsh.exe - see this answer for details.
[1] In interactive use, % characters cannot technically be escaped, but they're retained as-is unless they're part of a cmd.exe-style environment-variable reference that refers to an existing environment variable, such as %OS%. However, there are ways to treat % literally even in such cases, as discussed in this answer. These techniques are important for invoking cmd.exe command lines programmatically from outside cmd.exe, such as from Node.Js or Python, because such programmatic invocations - perhaps surprisingly - use the rules of interactive cmd.exe sessions, not batch files.
When I execute the following hello.ps1 in PowerShell, I get an output like I would expect, a Hello World! with a newline.
#powershell script hello.ps1
"Hello World!"
Output of run
PS C:\test>.\hello.ps1
Hello World!
PS C:\test>
But when I call the script from a bat file, I get two (2) newlines output in Windows console.
:: Windows CMD script name: mytest.bat
powershell -ExecutionPolicy Bypass hello.ps1
Now the output has two newlines, note the empty line after Hello World!.
C:\test>mytest.bat
Hello World!
C:\test
How can I avoid this second newline?
Mandragor -
Firstly, that is not two lines, it's just one.
Secondly, this is not a PowerShell specific thing.
You are calling one shell from another, and both must provide you a response and return to interactive mode. Calling an external command for one shell to another must be processed by the called shell first and results returned from the called shell to the calling shell, then exit the called process, and that calling shell must complete its process and any stdout stuff, and exit that process.
Double-clicking a bat/cmd file, starts cmd.exe, task manager shows
that process is running
In your bat/cmd you are calling another standalone exe, task manager
shows that the process is running. It must execute and return stdout
results, then close/exit.
cmd.exe the completes, and returns its stdout stuff, if any, and places the cursor at the next line for more interactive work.
Hence, the two responses, only if you ask for cmd.exe stdout stuff will you see it, vs just null.
You'll always have one more line return to get you back to the calling process.
Also, point of note:
Write-Host except in specific circumstances, and what you are doing
is not one of them.
You only need that bypass policy setting if you are in a restricted
environment. RemoteSigned is now the PowerShell default, meaning all local scripts will run, and remote scripts must be signed.
Simple strings should use single quotes in most cases. Double quotes
are for expanding variable content and other formatting scenarios.
Try the same thing calling any other shell, you'll get the same results. Heck, even calling another bat/cmd from a bat/cmd.
Example:
Running mytest.bat in cmd.exe that only contains these two lines, to show that only 1 CRLF is actually returned, per process, then you get bat the interactive shell.
powershell -File D:\Temp\hello.ps1
call d:\temp\hello.bat
# Results
Hello World!
D:\Temp>call d:\temp\hello.bat
D:\Temp>echo 'Hello World!'
'Hello World!'
D:\Temp>
You can change your powershell script like this:
#powershell script hello.ps1
if ($args[0] -eq "cmd") {
write-host "Hello World!" -nonewline
}
else {
"Hello World!"
}
Then change the caller batch file:
#echo off
powershell -executionpolicy Bypass TheNameOfPowershellScript.ps1 "cmd"
Now when run from cmd use the caller batch file and from powershell directly use the ps1 script. It will give the expected result(Tested).
I am using mr on Windows and it allows running arbitrary commands before/after any repository action. As far as I can see this is done simply by invoking perl's system function. However something seems very wrong with my setup: when making mr run the following batch file, located in d:
#echo off
copy /Y foo.bat bar.bat
I get errors on the most basic windows commands:
d:/foo.bat: line 1: #echo: command not found
d:/foo.bat: line 2: copy: command not found
To make sure mr isn't the problem, I ran perl -e 'system( "d:/foo.bat" )' but the output is the same.
Using xcopy instead of copy, it seems the xcopy command is found since the output is now
d:/foo.bat: line 1: #echo: command not found
Invalid number of parameters
However I have no idea what could be wrong with the parameters. I figured maybe the problem is the batch file hasn't full access to the standard command environment so I tried running it explicitly via perl -e 'system( "cmd /c d:\foo.bat" )' but that just starts cmd and does not run the command (I have to exit the command line to get back to the one where I was).
What is wrong here? A detailed explanation would be great. Also, how do I solve this? I prefer a solution that leaves the batch file as is.
The echo directive is executed directly by the running command-prompt instance.
But perl is launching a new process with your command. You need to run your script within a cmd instance, for those commands to work.
Your cmd /c must work. Check if you have spaces in the path you are supplying to it.
You can use a parametrized way of passing arguments,
#array = qw("/c", "path/to/xyz.bat");
system("cmd.exe", #array);
The echo directive is not an executable and hence, it errors out.
The same is true of the copy command also. It is not an executable, while xcopy.exe is.
Context: Oracle Enterprise Manager has a feature to "execute host command." If into that feature I enter "dir c:\temp" then the output window echos the command and then shows a directory listing. If into that feature I enter "powershell dir c:\temp" the output window shows only the echo'd command. No directory listing. If on the target machine I enter those two commands in both cases I get the echo'd command followed by a directory listing.
I hypothesize that what I see in the cmd.exe window on the client blends two stdout streams: one from the cmd.exe itself and one from the invoked process (powershell dir c:\temp). The Oracle thing seems to recognize only the cmd.exe's stdout.
Is there some way I can force the stdout from the invoked process to be in the cmd.exe's stdout stream so that Oracle will recognize it and the thing I am trying to build will work?
I don't think you can directly pipe the output from one program back into STDOUT of a parent cmd.exe - assuming that is what Oracle is doing at some level.
That being said, you could try something clever like the following:
cmd /c "powershell -Command ""& echo Hello" > %TEMP%\a.txt & TYPE %TEMP\a.txt
Basically this is capturing the output from PowerShell, placing it in a temporary file, then dumping that file back onto STDOUT in cmd.exe. A nice touch would be cleaning up the temp file with a & DEL %TEMP%\a.txt on the end of the command.
You will probably need to toy around with the command line to account for any quirks in how Oracle is passing things along - my guess is that it is invoking cmd.exe /c directly so you can probably leave that part off.
We have a lot of existing batch files that help with the running of different perl and ruby scripts.
A batch file (e.g. test.bat) would normally be invoked like:
$ test
and within the batch file, it will set some settings and finally try to run the corresponding script file (e.g. test.pl) like this:
perl -S "%0.pl" %*
All works with cmd.exe, but today, I decided to switch to PowerShell and found out that it expands the commands. So trying to run "test" will actually run "Full\path\test.bat" and my script would complain that there is no file test.bat.pl.
Is there a way to prevent this command expansion? Rewriting all batch files is not an option.
One way is to call cmd explicitly:
cmd /c test