The recommended way to run scripts is
powershell.exe -NonInteractive -Command " & some.ps1 "
However for example TeamCity PowerShell runner uses:
powershell.exe -NonInteractive -Command - < some.ps1
I do not have an idea what "- <" means and cannot find any information on subject. Any help?
Because powershell.exe is being invoked through the Windows shell, it is the same as if you were on a normal command prompt (cmd.exe). In that situation < pipes a file to the standard input (stdin) of the previous command. The help for powershell.exe states that if the value of -Command is simply -, the command text is read from standard input.
Here's a more self-documenting demonstration of < in cmd.exe:
processSomeFile.exe outputFileName.ext < intputFile.ext
If the value of Command is "-", the command text is read from
standard input.
< is just the stdout -> stdin redirection operator
I'd say that the recommaneded way to execute a script is with the -File parameter:
powershell.exe -NonInteractive -Command -File some.ps1
With regard to your question, the '<' character is reserved and cannot be used in PowerShell.
Using the I/O operators like > and < in a bash script means that you write output to an external file i.e. :
echo hello > hello.txt
or you get input from an external file i.e. :
psql -U test test < script.sql
the same logic is applied to the windows powershell, you can get more infos HERE
Related
I'm trying to execute the following command in PowerShell, but I have no idea how to escape the ampersand character which is part of the URL
az rest `
--method GET `
--uri ("https://graph.microsoft.com/v1.0/groups?`$count=true&`$filter=startsWith(displayName,'some+filter+text')&`$select=id,displayName") `
--headers 'Content-Type=application/json'
As the & character is used to start a new command, it breaks the url and want to execute the remainder.
Is there a way to tell powershell not to do that?
Olaf's answer provides an effective solution; let me add an explanation:
The source of the problem is a confluence of two behaviors:
When calling external programs, PowerShell performs on-demand double-quoting of each argument solely based on whether a given argument value contains spaces - otherwise, the argument is passed unquoted - irrespective of whether or not the value was originally quoted in the PowerShell command (e.g., cmd /c echo ab, cmd /c echo 'ab', and cmd /c echo "ab" all result in unquoted ab getting passed as the last token on the command line PowerShell rebuilds behind the scenes to ultimately use for execution).
The Azure az CLI is implemented as a batch file (az.cmd) and when a batch file is called, it is cmd.exe that parses the arguments given; surprisingly - and arguably inappropriately - it parses them as if the command had been submitted from inside a cmd.exe session.
As a result, if an argument is passed from PowerShell to a batch file that (a) contains no spaces, yet (b) contains cmd.exe metacharacters such as &, the call breaks.
A simple demonstration, using a cmd /c echo call as a stand-in for a call to a batch file:
# !! Breaks, because PowerShell (justifiably) passes *unquoted* a&b
# !! when it rebuilds the command line to invoke behind the scenes.
PS> cmd /c echo 'a&b'
a
'b' is not recognized as an internal or external command,
operable program or batch file.
There are three workarounds:
Use embedded "..." quoting:
# OK, but with a CAVEAT:
# Works as of PowerShell 7.2, but arguably *shouldn't*, because
# PowerShell should automatically *escape* the embedded " chars. as ""
PS> cmd /c echo '"a&b"'
"a&b"
# Ditto, using an *expandable* (interpolating) PowerShell string:
PS> cmd /c echo "`"$HOME & Family; can't put a `$ value on that.`""
"C:\Users\jdoe & Family; can't put a $ value on that." # e.g.
Use --%, the stop-parsing token - but see the bottom section of this answer for the limitations of --% and its associated pitfalls.
# OK, but with a CAVEAT:
# Requires "..." quoting, but doesn't recognize *PowerShell* variables,
# also doesn't support single-quoting and line continuation.
PS> cmd /c echo --% "a&b"
"a&b"
Call via cmd /c and pass a single string encompassing the batch-file call and all its arguments, (ultimately) using cmd.exe's syntax.
# OK (remember, cmd /c echo stands for a call to a batch file, such as az.cmd)
# Inside the single string passed to the outer cmd /c call,
# be sure to use "...", as that is the only quoting cmd.exe understands.
PS> cmd /c 'cmd /c echo "a&b"'
"a&b"
# Ditto, using an *expandable* (interpolating) PowerShell string:
PS> cmd /c "cmd /c echo `"$HOME & Family; can't put a `$ value on that.`""
"C:\Users\jdoe & Family; can't put a $ value on that." # e.g.
Taking a step back:
Now, wouldn't it be nice if you didn't have to worry about all these things?
Especially since you may not know or care if a given CLI - such as az - just so happens to be implemented as a batch file?
As a shell, PowerShell should do its best to relay arguments faithfully behind the scenes, and allow the caller to focus exclusively on satisfying only PowerShell's syntax rules:
Unfortunately, PowerShell has to date (PowerShell 7.2) generally done a very poor job in this regard, irrespective of cmd.exe's quirks - see this answer for a summary.
With respect to cmd.exe's (batch-file call) quirks, PowerShell could predictably compensate for them in a future version - but it looks like that isn't going to happen, unfortunately; see GitHub issue #15143.
I don't have access to an Azure tennant right now to test and I actually don't have experiences with the Azure CLI in general but I'd expect this to work:
az rest `
--method GET `
--uri 'https://graph.microsoft.com/v1.0/groups?$count=true&$filter=startsWith(displayName,some+filter+text)&$select=id,displayName' `
--headers 'Content-Type=application/json'
or this:
az rest --method GET --headers "Content-Type=application/json" `
--% --uri "https://graph.microsoft.com/v1.0/groups?$count=true&$filter=startsWith(displayName,some+filter+text)&$select=id,displayName"
I only added the backticks for better readability - you may remove them in your actual code.
I'd like to learn to execute a PowerShell command from another shell or language, e.g. Python os.system(). What I want to achieve is the following:
Execute the PowerShell command
Tee the output to both the console and a file
Return the command exit code
I think this gives an idea of what I would like to achieve, assuming to use cmd.exe as the caller environmnet:
powershell -NoProfile -command "& { cat foo.txt | Tee-Object ps-log.txt; exit $LASTEXITCODE }"
echo %errorlevel%
There are some problems here. First, I cannot use quotations in the command, e.g. :
powershell -NoProfile -command "& { cat `"foo bar.txt`" | Tee-Object ps-log.txt; exit $LASTEXITCODE }"
The cat argument seems to be passed unquoted and so cat looks for a 'bar.txt' parameter.
I think $LASTEXITCODE is expanded soon, that is before cat is executed.
& is inconvenient to use, because it does not accept a single command line string including arguments. An alternative to & is iex, however I cannot use it from cmd.exe. In fact:
powershell -NoProfile -command {iex cat foo.txt}
returns:
iex cat foo.txt
From cmd.exe, use the following (-c is short for -Command):
C:\>powershell -NoProfile -c "Get-Content \"foo bar.txt\" | Tee-Object ps-log.txt; exit -not $?"
There's no reason to use & { ... } in a string passed to -Command - just use ... instead.
Escape embedded " chars. as \" (PowerShell (Core) 7+ also accepts "").
Alternatively, as marsze's helpful answer points out, you can use '...' (single-quoting) inside the "..." string passed to -Command / -c, assuming that no string interpolation is required.
Since only PowerShell-native commands are involved in the command (on Windows, cat is simply an alias of Get-Content), $LASTEXITCODE is not set, as it only reflects the exit code of external programs. Instead, the automatic $? variable applies, which is a Boolean that indicates whether any errors were emitted by the commands in the most recently executed pipeline.
Negating this value with -not means that $true is converted to $false and $false to $true, and these values are converted to integers for the outside, with $false mapping to 0 and $true to 1.
Powershell supports single quotes, which saved me in such situations quite a lot of times. The good thing about it: They are unambiguous and easy to read. But mind that variable expansion won't work inside single-quoted strings.
powershell -NoProfile -command "cat 'foo bar.txt' | tee ps-log.txt"
Apart from that, have a look at the useful advice in mklement0's answer.
While implementing the suggestions in the topic Drag and Drop to a Powershell script we discovered that PowerShell consolidates multiple spaces into one. This is blocking us from dragging and dropping files through Windows Explorer into .bat/.ps1 constructs with multiple spaces.
This can easily be verified by the following command:
powershell -command "& echo {"a a"}"
This will result in:
a a
whereas the following result is expected:
a a
How can this be achieved?
Please note that your provided solution must fit into the Windows Explorer drag-and-drop .bat -> .ps1 flow. Currently in the .bat file we have:
powershell.exe -Command "& '%PSScript%' '%*'"
which obviously results in PowerShell stripping the duplicate spaces.
try this
powershell -command "& echo 'a a'"
Please Check if below solves your problem.
"Replace 10 with number of spaces you want."
powershell -command '& echo {"a"' ''.padleft(10, ' ') '"a"}'
output:
a a
works for me with a simple :
write-host a (" ") a
Im trying to run some PS scripts using the Powershell Runner in TC and defining my own script as "Source Code" instead of a script file.
My script is as simple as:
"Hello World!"
Im running on Windows Server 2008 R2 and ive tried with to:
Run it as x86 + x64
Using "Execute .ps1 with '-File' argument" + "Put script into powershell stdin with "-Command -" arguments.
Ive set the security policy to Unrestricted in an attempt to get it to work, but no luck.
If I instead use a Command Line runner and for example writes:
powershell -Command Get-ExecutionPolicy
It works fine.
The errors im getting (depending on which of the 2 execution modes im using) are:
Starting: C:\...\cmd.exe /c C:\...\powershell.exe -NonInteractive -Command
- "<C:\...\powershell3889347351955805274.ps1" && exit /b %ERRORLEVEL%
in directory: C:\...\e18dda4054c166c7
'-' was specified with the -Command parameter; no other arguments to -Command are permitted.
OR
Starting: C:\...\cmd.exe /c C:\...\powershell.exe -NonInteractive -File
"C:\...\powershell8264270201473986040.ps1" && exit /b %ERRORLEVEL%
in directory: C:\...\e18dda4054c166c7
The term 'f' is not recognized as the name of a cmdlet, function, script file,
It looks to me like TC puts something in the actual script itself, but im not sure. Im stuck and I cant figure out what point im missing here :S.
Can anyone help?
I wasn't able to reproduce this, but I noticed something pretty weird with the command that TeamCity was trying to run:
-NonInteractive -Command - "<C:\...\powershell3889347351955805274.ps1"
I did not see it adding the quotes for me, so I thought maybe TeamCity is trying to quote a path with space(s) in it ( would have helped if you hadn't redacted your path)
So I switched my agent to a path with a space in it and I got the same command, and yes, the same error. So TeamCity is quoting the path wrongly. It is including the < in the quotes while it should have been <"c:\path with\space"
I will see if I can file a bug for this ( if there isn't one)
Try moving your agent to a non-space path as a workaround.
For my tests I am using 'Start > Run' dialog (not the cmd.exe).
This works fine, and I get 9 in log.txt
powershell -Command 4+5 > c:\log.txt
But this does not work:
powershell -EncodedCommand IAA1ACsANwAgAA== > c:\log.txt
So how can I redirect output in this case?
Experimental code:
function Test
{
$cmd = { 5+7 }
$encodedCommand = EncodeCommand $cmd
StartProcess "powershell -Command $cmd > c:\log.txt"
StartProcess "powershell -EncodedCommand $encodedCommand > c:\log2.txt"
}
function StartProcess($commandLine)
{
Invoke-WMIMethod -path win32_process -name create -argumentList $commandLine
}
function EncodeCommand($expression)
{
$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)
[Convert]::ToBase64String($commandBytes)
}
The "Run" dialog doesn't seem like it provides redirection at all. According to this usenet post you only get redirection if you have a console. I wonder if the redirection parameter is being parsed by powershell.exe, which is choosing to redirect if it's not receiving encoded input? Sounds like a question for Raymond Chen.
Anyway, this works, at the expense of spawning an otherwise useless console:
cmd /c powershell -EncodedCommand IAA1ACsANwAgAA== > c:\ps.txt
The difference between those two commands is that the -Command parameter is greedy. It takes everything on the command line after it, while -EncodedCommand is not greedy. What the first command is really doing is:
powershell -Command "4+5 > c:\log.txt"
So the new PowerShell instance is handling the redirection. However, if you use the -EncodedCommand paramter, the new PowerShell instance does not see the redirection because you did not include it in the encoded command. This can be a bad thing if the environment calling PowerShell does not have redirection (like in a scheduled task).
So, as "crb" showed, you need to either encode the redirection into your command, or call PowerShell from an environment that can handle the redirection (like cmd, or another PowerShell instance).
I had to encode command together with redirection.
function Test
{
$cmd = { 5+7 }
$encodedCommand = EncodeCommand "$cmd > 'c:\log2.log'"
StartProcess "powershell -Command $cmd > c:\log.txt"
StartProcess "powershell -EncodedCommand $encodedCommand"
}
So this will write a sum 5+7 into c:\log2.log
powershell -EncodedCommand IAA1ACsANwAgACAAPgAgACcAYwA6AFwAbABvAGcAMgAuAGwAbwBnACcA
P.S.
crb suggested to use "cmd /c". But in this case the encoded script length will be constrained by the command line limitations
On computers running Microsoft Windows
XP or later, the maximum length of the
string that you can use at the command
prompt is 8191 characters. On
computers running Microsoft Windows
2000 or Windows NT 4.0, the maximum
length of the string that you can use
at the command prompt is 2047
characters.