Passing parameters to powershell script - windows

I'm trying to run a powershell script from the run dialog (will be used as a scheduled task), and I'm having troubles passing parameters.
The script will take in two parameters, named title and msg.
The script is located in: D:\Tasks Scripts\Powershell\script.ps1
This is what I'm trying to do:
powershell.exe -noexit 'D:\Tasks Scripts\Powershell\script.ps1' -title 'Hello world' -msg 'This is a test message'
But it fails upon reading the parameters.
Running .\script.ps1 -title 'Hello world' -msg 'This is a test message' on powershell works fine.

Use -file before the path to your script:
powershell.exe -noexit -file 'D:\Tasks Scripts\Powershell\script.ps1' etc...

I usually run powershell scripts from cmd.exe because this is portable
(works out-of-the-box on others' computers, like developer folks or clients):
no need to worry about Set-ExecutionPolicy or associating the .ps1 extension.
I create the file with .cmd extension (instead of .ps1), and copy&paste a short,
constant code to the first line(s) that invokes powershell.exe and passes the rest
of the file to it.
Passing arguments is tricky. I have multiple variants of the constant code
because the general case is painful.
when not passing arguments, the .cmd file looks like this:
#powershell -c ".(iex('{#'+(gc '%~f0' -raw)+'}'))" & goto :eof
# ...arbitrary PS code here...
write-host hello, world!
This uses the -Command argument of powershell.exe. Powershell reads the .cmd
file as text, puts it in a ScriptBlock with the first line commented out,
and evaluates it with the '.' command.
Further command line arguments
can be added to the Powershell invocation as required (e.g. -ExecutionPolicy Unrestricted,
-Sta etc.)
when passing arguments that do not contain spaces or are 'single-quoted'
(which is non-standard in cmd.exe), the one-liner is this:
#powershell -c ".(iex('{#'+(gc($argv0='%~f0') -raw)+'}'))" %* & goto :eof
write-host this is $argv0 arguments: "[$($args -join '] [')]"
param() declarations could be used as well, $args is not obligatory.
$argv0 is used to compensate for the missing $MyInvocation.PS* info.
Examples:
G:\>lala.cmd
this is G:\lala.cmd arguments: []
G:\>lala.cmd "1 2" "3 4"
this is G:\lala.cmd arguments: [1] [2] [3] [4]
G:\>lala.cmd '1 2' '3 4'
this is G:\lala.cmd arguments: [1 2] [3 4]
when passing arguments that are "double-quoted" but do not contain
the & and ' characters, I use a two-liner to replace all " with '
#echo off& set A= %*& set B=#powershell -c "$argv0='%~f0';.(iex('{'
%B%+(gc $argv0|select -skip 2|out-string)+'}'))" %A:"='%&goto :eof
write-host this is $argv0 arguments: "[$($args -join '] [')]"
(Note that the space is important in the A= %* assignment for the
argument-less case.)
Results:
G:\>lala.cmd
this is G:\lala.cmd arguments: []
G:\>lala.cmd "1 2" "3 4"
this is G:\lala.cmd arguments: [1 2] [3 4]
G:\>lala.cmd '1 2' '3 4'
this is G:\lala.cmd arguments: [1 2] [3 4]
the most general case passes the arguments via environment variables
thus Powershell's param() declaration does not work. In this case the
arguments are expected to be "double-quoted" and may contain ' or &
(except for the path of the .cmd file itself):
;#echo off & setlocal & set A=1& set ARGV0=%~f0
;:loop
;set /A A+=1& set ARG%A%=%1& shift& if defined ARG%A% goto :loop
;powershell -c ".(iex('{',(gc '%ARGV0%'|?{$_ -notlike ';*'}),'}'|out-string))"
;endlocal & goto :eof
for ($i,$arg=1,#(); test-path -li "env:ARG$i"; $i+=1) { $arg += iex("(`${env:ARG$i}).Trim('`"')") }
write-host this is $env:argv0 arguments: "[$($arg -join '] [')]"
write-host arg[5] is ($arg[5]|%{if($_){$_}else{'$null'}})
(Note that in the first line A=1& must not contain space.)
Result:
G:\>lala.cmd "a b" "c d" "e&f" 'g' "h^j"
this is G:\lala.cmd arguments: [a b] [c d] [e&f] ['g'] [h^j]
arg[5] is $null

Related

In YAD, how do I set a field value processing the values of other fields?

Example:
yad --form \
--field="A=":NUM "9" \
--field="B=":NUM "17" \
--field="A+B=":RO "?" \
--field="calc":FBTN "A=%1; B=%2; #echo 3:$((A+B))"
The A=%1 works. Calling another command instead of #echo 3: would work (for example using notify-send).
#echo 3:anyString works too, if it is the first sentence.
'#echo 3:$((%1+%2))' would set the value of the field "A+B=" to $((9+17)) instead of the result.
The following doesn't works too:
--field="calc":FBTN "#echo 3:$(adder %1 %2)"
(Assuming I have a function or command called adder)
Is there any other way to do this?
EDIT:
Strange for me:
--field="calc":FBTN "#echo 3:$(echo %1 + %2)"
uses the actual values of the fields 1 and 2: it works.
But any other command than echo does not work.
--field="calc":FBTN "#echo 3:$(expr %1 + %2)"
results in the message that "%1" isn't a number.
I'm using GNU bash, Version 5.0.17.

Batch Script - Escaping Double Quotes in variable

I want to run a script which only purpose is to execute its first argument, so I have the following script:
set command=%~1
%command%
And I run it like RunCommand.bat "echo hello world" which works.
Now I want to escape any special char, for example double quote. I tried a couple of options but non works. Any ideas?
This is my closest: RunCommand.bat "echo ""special char""" which prints-> ""special char""
EDIT
This script works
set "command=%~1"
set "command=%command:""="%"
%command%
refferenced from Escaping Double Quotes in Batch Script
No need to escape anything.
runcommand.bat:
#%*
(yes, that's all)
Output:
C:\temp>runcommand.bat echo "hello World!"
"hello World!"
C:\temp>runcommand.bat ping -n 1 www.google.com
Ping wird ausgeführt für www.google.com [216.58.205.228] mit 32 Bytes Daten:
Antwort von 216.58.205.228: Bytes=32 Zeit=13ms TTL=57
Ping-Statistik für 216.58.205.228:
Pakete: Gesendet = 1, Empfangen = 1, Verloren = 0
(0% Verlust),
Ca. Zeitangaben in Millisek.:
Minimum = 13ms, Maximum = 13ms, Mittelwert = 13ms
C:\temp>runcommand.bat wmic os get serialnumber /value
SerialNumber=00248-80000-00000-AR7GX
In response to my bat file must receive its variable inside a double quote:
I see no benefit in complicating things with additional quotes, when not needed, but here you go (still no escaping needed):
#cmd /c %*
Output:
C:\temp>runcommand "echo "special char"&echo hello"
"special char"
hello

Batch file to read a txt with special characters and replace a word in it

I'm trying to make a batch file that reads a txt file "ayylmao.txt" and find a specific word "hello" and replaces it with "xello".
The thing is that the "ayylmao.txt" contains specific characters.
Ayylmao.txt looks something like this:
‹‹R‹Ę‹/M‹;Ču‹č˙˙˙‹‹#‰‹‹#CëC;Đu‹čq˙˙˙‹‹#C‹D$‰;7u®‹Ó‹Ćčúţ˙˙„Ŕu3Ŕ‰YZ]_^[ĂŤ# SVWUÄđ‰$‹ô‹‰D$‹
‹‹#;Č‚† ‹Ř‹>_‹ůz;ßrv;Ču!‹B‹A‹B‹)B‹x uV‹čđţ˙˙ëM‹Ř‹>_‹ůz;ßu
‹B‹)Bë3‹Z‰\$‹>‹‹.}+ű‰|$+Č‹‰HŤT$‹čMţ˙˙„Ŕu3 hello Ŕë°ë‹‹ ‰‹;D$…Y˙˙˙3ŔÄ]_^[ĂSVW‹Ú‹đţ }ľ ëĆ˙˙ ć ˙˙‰sjh Vj
You can see the "hello" word in the last line. I want the batch to go to the process and give me a ayylmao1.txt that looks like this:
‹‹R‹Ę‹/M‹;Ču‹č˙˙˙‹‹#‰‹‹#CëC;Đu‹čq˙˙˙‹‹#C‹D$‰;7u®‹Ó‹Ćčúţ˙˙„Ŕu3Ŕ‰YZ]_^[ĂŤ# SVWUÄđ‰$‹ô‹‰D$‹
‹‹#;Č‚† ‹Ř‹>_‹ůz;ßrv;Ču!‹B‹A‹B‹)B‹x uV‹čđţ˙˙ëM‹Ř‹>_‹ůz;ßu
‹B‹)Bë3‹Z‰\$‹>‹‹.}+ű‰|$+Č‹‰HŤT$‹čMţ˙˙„Ŕu3 xello Ŕë°ë‹‹ ‰‹;D$…Y˙˙˙3ŔÄ]_^[ĂSVW‹Ú‹đţ }ľ ëĆ˙˙ ć ˙˙‰sjh Vj
You can see that "hello" is now "xello".
I found this batch file that replaces a word from a text file:
#echo off
REM -- Prepare the Command Processor --
SETLOCAL ENABLEEXTENSIONS
SETLOCAL DISABLEDELAYEDEXPANSION
if "%~1"=="" findstr "^::" "%~f0"&GOTO:EOF
for /f "tokens=1,* delims=]" %%A in ('"type %3|find /n /v """') do (
set "line=%%B"
if defined line (
call set "line=echo.%%line:%~1=%~2%%"
for /f "delims=" %%X in ('"echo."%%line%%""') do %%~X
) ELSE echo.
)
This code works for files that don't have specific characters very good if use it like this:
code.bat "hello" "xello" "ayylmao.txt">"ayylmao1.txt"
This code only types in ayylmao1.txt few special characters but replaces hello. I want all the special characters typed in there.
I made it like this:
chcp 1252
code.bat "hello" "xello" "ayylmao.txt">"ayylmao1.txt"
But it didn't work. It worked just like the first code.
If there is a way in PowerShell to do this I'd be glad to hear it.
What you have there looks like a binary file, not a text file, despite the extension. Batch is no good for editing binary files. In PowerShell it's doable, but you need to resort to working with the data bytes instead of simple text.
This is a basic example that will find the first occurrence of the string "hello" in your file and replace it with "xhello":
$f = 'C:\path\to\ayylmao.txt'
$stext = 'hello'
$rtext = [char[]]'xhello'
$len = $stext.Length
$offset = $len - 1
$data = [IO.File]::ReadAllBytes($f)
# find first occurrence of $stext in byte array
for ($i=0; $i -lt $data.Count - $offset; $i++) {
$slice = $data[$i..($i+$offset)]
if (-join [char[]]$slice -eq $stext) { break }
}
# Once you know the beginning ($i) and length ($len) of the array slice
# containing $stext you can "cut up" $data and concatenate the slices before
# and after $stext to the byte sequence you want to insert ($rtext):
#
# |<-- $stext -->|
# [...]['h','e','l','l','o'][...] <-- $data
# ^ ^ ^ ^
# | | | |
# | $i | $i+$len
# $i-1 $i+$offset (== $i+$len-1)
#
$rdata = $data[0..($i-1)] + [byte[]]$rtext + $data[($i+$len)..($data.Count-1)]
[IO.File]::WriteAllBytes($f, $rdata)
You'll need to adjust this code if you want the replacement to work differently (replace other occurrences as well, replace a different occurrence, …).
But it didn't work. It worked just like the first code. Help ?
This batch code is coming from this site and there is a link to discussion why it doesn't work with special characters.
Yes, the PowerShell replace command can replace the string and keep the special characters. To call it from within your batch script, use the following line
powershell -command "(get-content Ayylmao.txt) -replace 'hello','xello' | set-content Ayylmao.txt"
If you want to enter your parameters from the command line, then the line would be
powershell -command "(get-content %3) -replace '%1','%2' | set-content %4"
And if you want to use variables defined in the batch script, it is the same as you would for any batch script
set file=Ayylmao.txt
set Search_criteria=hello
set Replace_criteria=xello
powershell -command "(get-content %file%) -replace '%Search_criteria%','%Replace_criteria%' | set-content %file%"

How do I pass a literal double quote from PowerShell to a native command?

I'd like to print a string literal in AWK / gawk using the PowerShell command line (the specific program is unimportant). However, I think I misunderstand the quoting rules somewhere along the line -- PowerShell apparently removes double quotes inside single quotes for native commands, but not when passing them to commandlets.
This works in Bash:
bash$ awk 'BEGIN {print "hello"}'
hello <-- GOOD
And this works in PowerShell -- but importantly I have no idea why the escaping is needed:
PS> awk 'BEGIN {print \"hello\"}'
hello <-- GOOD
This prints nothing in PowerShell:
PS> awk 'BEGIN {print "hello"}'
<-- NOTHING IS BAD
If this really is the only way of doing this in PowerShell, then I'd like to understand the chain of quoting rules that explains why. According to the PowerShell quoting rules at About Quoting Rules, this shouldn't be necessary.
BEGIN SOLUTION
The punchline, courtesy of Duncan below, is that you should add this function to your PowerShell profile:
filter Run-Native($command) { $_ | & $command ($args -replace'(\\*)"','$1$1\"') }
Or specifically for AWK:
filter awk { $_ | gawk.exe ($args -replace'(\\*)"','$1$1\"') }
END SOLUTION
The quotes are properly passed to PowerShell's echo:
PS> echo '"hello"'
"hello" <-- GOOD
But when calling out to an external "native" program, the quotes disappear:
PS> c:\cygwin\bin\echo.exe '"hello"'
hello <-- BAD, POWERSHELL REMOVED THE QUOTES
Here's an even cleaner example, in case you're concerned that Cygwin might have something to do with this:
echo #"
>>> // program guaranteed not to interfere with command line parsing
>>> public class Program
>>> {
>>> public static void Main(string[] args)
>>> {
>>> System.Console.WriteLine(args[0]);
>>> }
>>> }
>>> "# > Program.cs
csc.exe Program.cs
.\Program.exe '"hello"'
hello <-- BAD, POWERSHELL REMOVED THE QUOTES
DEPRECATED EXAMPLE for passing to cmd, which does its own parsing (see Etan's comment below):
PS> cmd /c 'echo "hello"'
"hello" <-- GOOD
DEPRECATED EXAMPLE for passing to Bash, which does its own parsing (see Etan's comment below):
PS> bash -c 'echo "hello"'
hello <-- BAD, WHERE DID THE QUOTES GO
Any solutions, more elegant workarounds, or explanations?
The problem here is that the Windows standard C runtime strips unescaped double quotes out of arguments when parsing the command line. PowerShell passes arguments to native commands by putting double quotes around the arguments, but it doesn't escape any double quotes that are contained in the arguments.
Here's a test program that prints out the arguments it was given using the C stdlib, the 'raw' command line from Windows, and the Windows command line processing (which seems to behave identically to the stdlib):
C:\Temp> type t.c
#include <stdio.h>
#include <windows.h>
#include <ShellAPI.h>
int main(int argc,char **argv){
int i;
for(i=0; i < argc; i++) {
printf("Arg[%d]: %s\n", i, argv[i]);
}
LPWSTR *szArglist;
LPWSTR cmdLine = GetCommandLineW();
wprintf(L"Command Line: %s\n", cmdLine);
int nArgs;
szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if( NULL == szArglist )
{
wprintf(L"CommandLineToArgvW failed\n");
return 0;
}
else for( i=0; i<nArgs; i++) printf("%d: %ws\n", i, szArglist[i]);
// Free memory allocated for CommandLineToArgvW arguments.
LocalFree(szArglist);
return 0;
}
C:\Temp>cl t.c "C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib"
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
t.c
Microsoft (R) Incremental Linker Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:t.exe
t.obj
"C:\Program Files (x86)\Windows Kits\8.1\lib\winv6.3\um\x86\shell32.lib"
Running this in cmd we can see that all unescaped quotes are stripped, and spaces only separate arguments when there have been an even number of unescaped quotes:
C:\Temp>t "a"b" "\"escaped\""
Arg[0]: t
Arg[1]: ab "escaped"
Command Line: t "a"b" "\"escaped\""
0: t
1: ab "escaped"
C:\Temp>t "a"b c"d e"
Arg[0]: t
Arg[1]: ab
Arg[2]: cd e
Command Line: t "a"b c"d e"
0: t
1: ab
2: cd e
PowerShell behaves a bit differently:
C:\Temp>powershell
Windows PowerShell
Copyright (C) 2012 Microsoft Corporation. All rights reserved.
C:\Temp> .\t 'a"b'
Arg[0]: C:\Temp\t.exe
Arg[1]: ab
Command Line: "C:\Temp\t.exe" a"b
0: C:\Temp\t.exe
1: ab
C:\Temp> $a = "string with `"double quotes`""
C:\Temp> $a
string with "double quotes"
C:\Temp> .\t $a nospaces
Arg[0]: C:\Temp\t.exe
Arg[1]: string with double
Arg[2]: quotes
Arg[3]: nospaces
Command Line: "C:\Temp\t.exe" "string with "double quotes"" nospaces
0: C:\Temp\t.exe
1: string with double
2: quotes
3: nospaces
In PowerShell, any argument that contains spaces is enclosed in double quotes. Also the command itself gets quotes even when there aren't any spaces. Other arguments aren't quoted even if they include punctuation such as double quotes, and and I think this is a bug PowerShell doesn't escape any double quotes that appear inside the arguments.
In case you're wondering (I was), PowerShell doesn't even bother to quote arguments that contain newlines, but neither does the argument processing consider newlines as whitespace:
C:\Temp> $a = #"
>> a
>> b
>> "#
>>
C:\Temp> .\t $a
Arg[0]: C:\Temp\t.exe
Arg[1]: a
b
Command Line: "C:\Temp\t.exe" a
b
0: C:\Temp\t.exe
1: a
b
The only option since PowerShell doesn't escape the quotes for you seems to be to do it yourself:
C:\Temp> .\t 'BEGIN {print "hello"}'.replace('"','\"')
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}"
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
To avoid doing that every time, you can define a simple function:
C:\Temp> function run-native($command) { & $command $args.replace('\','\\').replace('"','\"') }
C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And "another"'
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Arg[2]: And "another"
Command Line: "C:\Temp\t.exe" "BEGIN {print \"hello\"}" "And \"another\""
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
2: And "another"
N.B. You have to escape backslashes as well as double quotes otherwise this doesn't work (this doesn't work, see further edit below):
C:\Temp> run-native .\t 'BEGIN {print "hello"}' 'And \"another\"'
Arg[0]: C:\Temp\t.exe
Arg[1]: BEGIN {print "hello"}
Arg[2]: And \"another\"
Command Line: "C:\Temp\t.exe" "B EGIN {print \"hello\"}" "And \\\"another\\\""
0: C:\Temp\t.exe
1: BEGIN {print "hello"}
2: And \"another\"
Another edit: Backslash and quote handling in the Microsoft universe is even weirder than I realised. Eventually I had to go and read the C stdlib sources to find out how they interpret backslashes and quotes:
/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
2N+1 backslashes + " ==> N backslashes + literal "
N backslashes ==> N backslashes */
So that means run-native should be:
function run-native($command) { & $command ($args -replace'(\\*)"','$1$1\"') }
and all backslashes and quotes will survive the command line processing. Or if you want to run a specific command:
filter awk() { $_ | awk.exe ($args -replace'(\\*)"','$1$1\"') }
(Updated following #jhclark's comment: it needs to be a filter to allow piping into stdin.)
You get different behavior, because you're using 4 different echo commands, and in different ways on top of that.
PS> echo '"hello"'
"hello"
echo is PowerShell's Write-Output cmdlet.
This works, because the cmdlet takes the given argument string (the text within the outer set of quotes, i.e. "hello") and prints that string to the success output stream.
PS> c:\cygwin\bin\echo '"hello"'
hello
echo is Cygwin's echo.exe.
This doesn't work, because the double quotes are removed from the argument string (the text within the outer set of quotes, i.e. "hello") when PowerShell calls the external command.
You get the same result if for instance you call echo.vbs '"hello"' with WScript.Echo WScript.Arguments(0) being the content of echo.vbs.
PS> cmd /c 'echo "hello"'
"hello"
echo is CMD's built-in echo command.
This works, because the command string (the text within the outer set of quotes, i.e. echo "hello") is run in CMD, and the built-in echo command preserves the argument's double quotes (running echo "hello" in CMD produces "hello").
PS> bash -c 'echo "hello"'
hello
echo is bash's built-in echo command.
This doesn't work, because the command string (the text within the outer set of quotes, i.e. echo "hello") is run in bash.exe, and its built-in echo command does not preserve the argument's double quotes (running echo "hello" in bash produces hello).
If you want Cygwin's echo to print outer double quotes you need to add an escaped pair of double quotes to your string:
PS> c:\cygwin\bin\echo '"\"hello\""'
"hello"
I would've expected this to work for the bash-builtin echo es well, but for some reason it doesn't:
PS> bash -c 'echo "\"hello\""'
hello
Quoting rules can get confusing when you're calling commands directly from PowerShell. Instead, I regularly recommend that people use the Start-Process cmdlet, along with its -ArgumentList parameter.
Start-Process -Wait -FilePath awk.exe -ArgumentList 'BEING {print "Hello"}' -RedirectStandardOutput ('{0}\awk.log' -f $env:USERPROFILE);
I don't have awk.exe (does that come from Cygwin?), but that line should work for you.

cmd script that fails when text is found

In the windows shell:
echo "foo bar" | find "foo"
succeeds (i.e. errorlevel = 0). However, I want a script that fails (i.e. errorlevel <> 0) when it finds a particular word in some input text. Any ideas?
A lttle trickery can emulate what you desire. Only the first three lines are required, the rest are just a test.
c:> echo "foo bar" | find "foo"
c:> if x%errorlevel%==x0 echo 1 | find "2"
c:> if not x%errorlevel%==x0 echo 1 | find "1" >nul 2>nul
c:> echo %errorlevel%
1

Resources