Mercurial - How to configure multiline hook on Windows - powershell

Running Windows 7, configuring a commit hook in my .hgrc file.
The hook calls an external powershell script and passes it a few parameters. When I put all the parameters on one long line, the script receives them just fine. However, when I try to put each parameter on its own line, the hook can't figure out that they are all part of the same call to my external script.
[hooks]
commit.working_one_liner = PowerShell.exe -ExecutionPolicy Bypass -File .\MyScript.ps1 -hg %HG% -updatedToChangeset %HG_NODE% -dbName 'Test'
commit.multi_line_hook_not_working = PowerShell.exe
-ExecutionPolicy Bypass
-File .\MyScript.ps1
-hg %HG%
-updatedToChangeset %HG_NODE%
-dbName 'Test'
According to the Hg documentation, I should be able to do this:
"A configuration file consists of sections, led by a [section] header and followed by name = value entries (sometimes called configuration keys):
[spam]
eggs=ham
green=
eggs
Each line contains one entry. If the lines that follow are indented, they are treated as continuations of that entry. Leading whitespace is removed from values. Empty lines are skipped. Lines beginning with # or ; are ignored and may be used to provide comments."
Despite indenting, the multi_line_hook seems to ignore everything after the first line. I've tried various escape characters (`, ^, \, etc.) after each new line. Any ideas what I'm missing here?

The example from the documentation only works for the first line to follow and not the rest.
I don't have access to windows at the moment, but on linux I need to use a backslash:
[hooks]
commit.working = echo \
test

Related

Awk Syntax error when trying to print json to individual files [duplicate]

In pwsh call the following:
Write-Host '{"drop_attr": "name"}'
Result ok:
{"drop_attr": "name"}
Now do the same via pwsh:
pwsh -Command Write-Host '{"drop_attr": "name"}'
Result is missing quotation marks and square brackets?
drop_attr: name
Update:
PowerShell 7.3.0 mostly fixed the problem, with selective exceptions on Windows, and it seems that in some version after 7.3.1 the fix will require opt-in - see this answer for details.
For cross-version, cross-edition code, the Native module discussed at the bottom may still be of interest.
Unfortunately, PowerShell's handling of passing arguments with embedded " chars. to external programs - which includes PowerShell's own CLI (pwsh) - is fundamentally broken (and always has been), up to at least PowerShell 7.2.x:
You need to manually \-escape " instances embedded in your arguments in order for them to be correctly passed through to external programs (which happens to be PowerShell in this case as well):
# Note: The embedded '' sequences are the normal and expected
# way to escape ' chars. inside a PowerShell '...' string.
# What is *unexpected* is the need to escape " as \"
# even though " can normally be used *as-is* inside a '...' string.
pwsh -Command ' ''{\"drop_attr\": \"name\"}'' '
Note that I'm assuming your intent is to pass a JSON string, hence the inner '' ... '' quoting (escaped single quotes), which ensures that pwsh ultimately sees a single-quoted string ('...'). (No need for an explicit output command; PowerShell implicitly prints command and expression output).
Another way to demonstrate this on Windows is via the standard choice.exe utility, repurposed to simply print its /m (message) argument (followed by verbatim [Y,N]?Y):
# This *should* preserve the ", but doesn't as of v7.2
PS> choice /d Y /t 0 /m '{"drop_attr": "name"}'
{drop_attr: name} [Y,N]?Y # !! " were REMOVED
# Only the extra \-escaping preserves the "
PS> choice /d Y /t 0 /m '{\"drop_attr\": \"name\"}'
{"drop_attr": "name"} [Y,N]?Y # OK
Note that from inside PowerShell, you can avoid the need for \-escaping, if you call pwsh with a script block ({ ... }) - but that only works when calling PowerShell itself, not other external programs:
# NOTE: Works from PowerShell only.
pwsh -Command { '{"drop_attr": "name"}' }
Background info on PowerShell's broken handling of arguments with embedded " in external-program calls, as of PowerShell 7.2.1:
This GitHub docs issue contains background information.
GitHub issue #1995 discusses the problem and the details of the broken behavior as well as manual workarounds are summarized in this comment; the state of the discussion as of PowerShell [Core] 7 seems to be:
A fix is being considered as an experimental feature, which may become an official feature, in v7.3 at the earliest. Whether it will become a regular feature - i.e whether the default behavior will be fixed or whether the fix will require opt-in or even if the feature will become official at all - remains to be seen.
Fixing the default behavior would substantially break backward compatibility; as of this writing, this has never been allowed, but a discussion as to whether to allow breaking changes in the future and how to manage them has begun: see GitHub issue #13129.
See GitHub PR #14692 for the relevant experimental feature, which, however, as of this writing is missing vital accommodations for batch files and msiexec-style executables on Windows - see GitHub issue #15143.
In the meantime, you can use the PSv3+ ie helper function from the Native module (in PSv5+, install with Install-Module Native from the PowerShell Gallery), which internally compensates for all broken behavior and allows passing arguments as expected; e.g.,
ie pwsh -Command ' ''{"drop_attr": "name"}'' ' would then work properly.
Another way. Are you in Windows or Unix?
pwsh -c "[pscustomobject]#{drop_attr='name'} | convertto-json -compress"
{"drop_attr":"name"}
Another way is to use "encoded commands".
> $cmd1 = "Write-Host '{ ""description"": ""Test program"" }'"
> pwsh -encoded ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd1)))
{ "description": "Test program" }

Passing a semicolon as a parameter to a .bat file from both cmd and PowerShell

This question seems to be rather simple, but even after searching the web for a couple of hours, I was not able to find a solution...
I have a batch file test.bat
set MY_VARIABLE=%~1
echo %MY_VARIABLE%
The point is that I want to call this programm with a semicolon as input parameter, i.e.,
.\test.bat ";",
from both cmd and Windows PowerShell. While this works fine from cmd, PowerShell does not seem to get anything as an input. Is there any way to make this work for both simultaneously?
This is because of command line syntax. The semicolon is one of multiple delimiters, that split the command line into words: a;b would be interpreted as two separate arguments a (%1) and b (%2).
Therefore, quotes are required. Since Powershell uses quotes for string literals (Powershell does its own re-quoting behind the scenes when passing arguments), you need to include them in the string:
.\test.bat '";"'
# or
.\test.bat "`";`""
Or as #mklement0 pointed out, the stop-parsing symbol --% would also be an option:
.\test.bat --% ";"
Note that this is specific to Powershell syntax.
In CMD, this will suffice:
test.bat ";"

pwsh -Command is removing quotation marks

In pwsh call the following:
Write-Host '{"drop_attr": "name"}'
Result ok:
{"drop_attr": "name"}
Now do the same via pwsh:
pwsh -Command Write-Host '{"drop_attr": "name"}'
Result is missing quotation marks and square brackets?
drop_attr: name
Update:
PowerShell 7.3.0 mostly fixed the problem, with selective exceptions on Windows, and it seems that in some version after 7.3.1 the fix will require opt-in - see this answer for details.
For cross-version, cross-edition code, the Native module discussed at the bottom may still be of interest.
Unfortunately, PowerShell's handling of passing arguments with embedded " chars. to external programs - which includes PowerShell's own CLI (pwsh) - is fundamentally broken (and always has been), up to at least PowerShell 7.2.x:
You need to manually \-escape " instances embedded in your arguments in order for them to be correctly passed through to external programs (which happens to be PowerShell in this case as well):
# Note: The embedded '' sequences are the normal and expected
# way to escape ' chars. inside a PowerShell '...' string.
# What is *unexpected* is the need to escape " as \"
# even though " can normally be used *as-is* inside a '...' string.
pwsh -Command ' ''{\"drop_attr\": \"name\"}'' '
Note that I'm assuming your intent is to pass a JSON string, hence the inner '' ... '' quoting (escaped single quotes), which ensures that pwsh ultimately sees a single-quoted string ('...'). (No need for an explicit output command; PowerShell implicitly prints command and expression output).
Another way to demonstrate this on Windows is via the standard choice.exe utility, repurposed to simply print its /m (message) argument (followed by verbatim [Y,N]?Y):
# This *should* preserve the ", but doesn't as of v7.2
PS> choice /d Y /t 0 /m '{"drop_attr": "name"}'
{drop_attr: name} [Y,N]?Y # !! " were REMOVED
# Only the extra \-escaping preserves the "
PS> choice /d Y /t 0 /m '{\"drop_attr\": \"name\"}'
{"drop_attr": "name"} [Y,N]?Y # OK
Note that from inside PowerShell, you can avoid the need for \-escaping, if you call pwsh with a script block ({ ... }) - but that only works when calling PowerShell itself, not other external programs:
# NOTE: Works from PowerShell only.
pwsh -Command { '{"drop_attr": "name"}' }
Background info on PowerShell's broken handling of arguments with embedded " in external-program calls, as of PowerShell 7.2.1:
This GitHub docs issue contains background information.
GitHub issue #1995 discusses the problem and the details of the broken behavior as well as manual workarounds are summarized in this comment; the state of the discussion as of PowerShell [Core] 7 seems to be:
A fix is being considered as an experimental feature, which may become an official feature, in v7.3 at the earliest. Whether it will become a regular feature - i.e whether the default behavior will be fixed or whether the fix will require opt-in or even if the feature will become official at all - remains to be seen.
Fixing the default behavior would substantially break backward compatibility; as of this writing, this has never been allowed, but a discussion as to whether to allow breaking changes in the future and how to manage them has begun: see GitHub issue #13129.
See GitHub PR #14692 for the relevant experimental feature, which, however, as of this writing is missing vital accommodations for batch files and msiexec-style executables on Windows - see GitHub issue #15143.
In the meantime, you can use the PSv3+ ie helper function from the Native module (in PSv5+, install with Install-Module Native from the PowerShell Gallery), which internally compensates for all broken behavior and allows passing arguments as expected; e.g.,
ie pwsh -Command ' ''{"drop_attr": "name"}'' ' would then work properly.
Another way. Are you in Windows or Unix?
pwsh -c "[pscustomobject]#{drop_attr='name'} | convertto-json -compress"
{"drop_attr":"name"}
Another way is to use "encoded commands".
> $cmd1 = "Write-Host '{ ""description"": ""Test program"" }'"
> pwsh -encoded ([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd1)))
{ "description": "Test program" }

Powershell (?) transforms argument in a very weird way - removes comma from string

I have a powershell build step in TeamCity:
param ([string] $a)
Write-Host "`$a is '$a'."
and in this step I set parameter $a as -a "%TestParam%" or as "-a %TestParam%", where TestParam has two lines abra and cadabra.
When I run the build I get the following output:
[Step 1/10] PowerShell arguments: -NoProfile, NonInteractive, -ExecutionPolicy, ByPass, -File, C:\buildAgent\temp\buildTmp\powershell1746295357460795314.ps1, -a, "abra, cadabra"
[Step 1/10] $a is 'abra cadabra'.
The only question I have: What on earth happened to the comma? Why has it disappeared?
If I do not use quotes at all (-a %TestParam%), then TeamCity passes each line as a separate parameter and I see $a is 'abra'..
Mathias R. Jessen's answer explains PowerShell's parsing of ,-separated tokens as arguments [his answer has since been deleted, but I hope it will be undeleted], but that doesn't apply in the case at hand, because any arguments passed to PowerShell's CLI via -File are not subject to PowerShell's command-line parsing - instead, such arguments are treated as literals.
That is, if the command line invoked by TeamCity truly were the following:
powershell ... -File C:\...ps1 -a "abra, cadabra"
then parameter variable $a would receive value abra, cadabra, as expected.
In other words: What is actually being passed in your case must be abra cadabra, not
abra, cadabra, so you need to revise the value of %TestParam% to ensure that it actually contains the desired comma.
As for why the log of the command invoked suggests that there is a , present in what you're passing:
I can only speculate, based on your own guess:
I suspect TeamCity of being a liar, showing lines joined with comma, but passing them without it.
Perhaps TeamCity, when logging invocation of a command line, naively breaks that command line into tokens by whitespace only, without considering quoting, and then presents them as a ,-separated list.
If this is indeed the case, then argument "abra cadabra" - without comma - would be logged as
"abra, cadabra", which would explain the confusion.

Powershell fails to run multi-line commands from stdin?

I'm wanting to pass arbitrary scripts to Powershell via stdin.
(In practice, I'd like to avoid having to put the script into a temporary file, but for the purposes of this question I will pipe the contents of a file to powershell.)
So I'm doing something like so (in this example, from a Windows cmd shell):
type myfile.txt | powershell -
It works if myfile.txt contains something like this:
1..3 | % { $_ *2 }
echo done
(It outputs 2\n4\n6\ndone.)
However, if I split this first statement across multiple lines like so, then Powershell simply exists without generating any output at all:
1..3 |
% { $_ *2 }
echo done
This seems to fail for any multiline statement. For example, this also fails to produce output:
1..3 | % {
$_ *2 }
echo done
I'm surprised by this since each are legal Powershell scripts that would work normally if placed into a .ps1 file and run as normal.
I've tried various things including escaping the EOL using line continuation chars, to no avail. The same effect occurs if the parent shell is Powershell, or even Python (using subprocess.Popen with stdin=PIPE). In each case, Powershell exits without any error, and the exit code is 0.
Interestingly, if I run the following, only "before.txt" gets created.
"before" | out-file before.txt
1..3 |
% { $_ *2 }
"after" | out-file after.txt
echo done
Any ideas why Powershell would have trouble reading a multi-line command, if read from stdin?
I'm going to consider this answered by this:
How to end a multi-line command in PowerShell since it shows that an extra newline is required.
However, I'm going to raise this to MS as a bug since this should not be required when reading from a non-tty, or when -NonInteractive switch is specified.
Please vote on my bug report to the Powershell team.
This is not a complete answer, but from what I can tell, the problem has to do with the input being sent in line by line.
To demonstrate the line-by-line issue, I invoke powershell this way:
powershell.exe -command "gc myfile.txt" | powershell.exe -
vs
powershell.exe -command "gc myfile.txt -raw" | powershell.exe -
The first example replicates what you see with type, the second reads the entire contents of the file, and it works as expected.
It also works from within PowerShell if you put the script contents in a string and pipe it into powershell.exe -.
I had a theory that it had to do with line-by-line input lacking line breaks, but it's not so clear cut. If that were the case, why would the first option work but not the second (removing the line break splitting the single pipeline should have no effect, while removing the line break between the pipeline and the echo should make it fail). Maybe there's something unclear about the way powershell is handling the input with or without line breaks.