Passing newline character to PowerShell via Cmd - powershell

I'm trying to run a PowerShell script from Windows cmd.exe. The input to the PowerShell script is a string, which contains newline characters using PowerShell backtick escaping - i.e:
`r`n
For demonstration purposes, the input string is then written to the console, and also dumped to a file.
The issue I have is that when the script is run from cmd.exe using the syntax
powershell.exe script.ps1 "TEST`r`nTEST"
The newline characters in the string are not treated as newline, and are included literally in both the console output and the output text file.
TEST`r`nTEST
However, if I run this from a PowerShell environment, I get the expected result (i.e. the newline characters are parsed correctly, and a newline is inserted in the appropriate location).
TEST
TEST
Similarly, if I pass in \r\n instead of the escaped newline characters through Windows cmd.exe, and do a .replace in the PowerShell script
$date = $data.replace("\r\n","`r`n")
I get the expected output:
TEST
TEST
Is anyone able to shed some light on why this happens?
The test script is as follows:
param([string]$data) # data to send
Write-Host $data
[IO.File]::WriteAllText('d:\temp.txt', $data)
return 0
And the file is called from the command line as:
powershell.exe script.ps1 "TEST`r`nTEST"
The script is running on Windows Server 2012 R2, using PowerShell v4.0

tl;dr
Use -Command and pass the entire PowerShell command as a single string; e.g.:
C:\> powershell -NoProfile -Command "script.ps1 \"TEST`r`nTEST\""
TEST
TEST
Note how the internal " instances are escaped as \", which PowerShell requires when called from the outside (alternatively, for full robustness, use "^"" (sic) in Windows PowerShell and "" in PowerShell (Core) v6+).
In your specific case,
powershell -NoProfile -Command script.ps1 "TEST`r`nTEST" would have worked too, but generally that only works as intended if the string has no embedded spaces.
Given that -Command is the default up to PSv5.1, your command - as currently posted - should work as-is.
As of PowerShell v5.1, arguments passed to powershell.exe from the outside:
ARE subject to interpretation by PowerShell, including string interpolation, by default and when you use -Command (i.e., specifying neither -File nor -Command currently defaults to -Command).
Caveat: The default behavior will change in v6: -File will be the default then - see the relevant change on GitHub.
are NOT subject to interpretation if you use -File to invoke a script - (after potential interpretation by cmd.exe) PowerShell treats all arguments as literal strings.
Caveat: This behavior is currently being discussed with respect to v6, given that it is overtly problematic in at least one case: trying to pass Boolean values.
Optional reading: Why you should pass the entire PowerShell command as a single argument when using -Command:
When you use -Command with multiple arguments, PowerShell essentially assembles them into a single command line behind the scenes before executing it.
Any "..."-quoting around the individual arguments is lost in the process, which can have unexpected results; e.g.:
C:\> powershell -NoProfile -Command "& { $args.count }" "line 1`r`nline 2"
3 # !! "line 1`r`nline 2" was broken into 3 arguments
Given that the outer "..." quoting was removed in the process of parsing the command line, the actual command line that PowerShell ended up executing was:
C:\ PS> & { $args.Count } line 1`r`nline 2
3
To illustrate why, let's look at an equivalent command that uses explicit quoting:
C:\ PS> & { $args.Count } "line" "1`r`nline" "2"
In other words: After the enclosing " were removed, the resulting token was broken into multiple arguments by spaces, as usual.

The parameter will need to be reinterpreted as a PowerShell string. Will this get you down the road?
The reason your -replace did not work is that the original string actually contains a backtick. It needs to be escaped in the search string.
C:\src\t>type p1.ps1
Param([string]$s)
Write-Host $s
$p = Invoke-Expression `"$s`"
Write-Host $p
$p2 = $s -replace "``r``n","`r`n"
Write-Host $p2
C:\src\t>powershell -noprofile -file .\p1.ps1 "TEST`r`nTEST"
TEST`r`nTEST
TEST
TEST
TEST
TEST

Carriage return and Linefeed are bytes with values 13 and 10, you can't write them, you can't see them.
As a convenience, when writing PowerShell code, the language will let you write:
"`r`n"
in a double quoted string, and when processing PowerShell source code (and at no other time), it will read those and replace them with bytes value 13 and 10.
It is this line of code in the PowerShell tokenizer which does it.
There is nothing special about backtick-n to the cmd.exe interpreter, and nothing special about having it in a string - you can put it there in a single quoted string
'`n'
or replacing it in a string - except that you have to note when the replacement happens. e.g. in your comment:
For example, if you pass in 'r'n and then replace 'r'n with 'r'n, the 'r'n is still output literally
Because your code
-replace "`r`n"
becomes
-replace "[char]13[char]10"
and your string passed in from outside contains
`r`n
and they don't match. Backtick-n in a string isn't magic, strings are not all interpreted by the PowerShell engine as PowerShell code, nor are parameters, or anything. And it's only in that context - when you write your -replace code, that is when the swap for actual newline characters happens.

Related

How to solve problem with quotes in arguments on calling a PowerShell script with -File option?

I want to call a small PowerShell (v5.1) script from Windows 10 Pro command line with arguments.
param($ReqURL, $JSONString)
Write-Host $ReqURL # for debug
Write-Host $JSONString # for debug
Invoke-RestMethod -Uri $ReqURL -Method Patch -ContentType "application/json" -Body $JSONString
Calling the script in a PowerShell console works fine:
.\http-patch.ps1 -ReqURL myRESTApi -JSONString '{"lastname": "Bunny"}'
Calling the script from the Windows Command Prompt (cmd.exe) works when I escape the double quotes, like this:
PowerShell .\http-patch.ps1 -ReqURL myRESTApi -JSONString '{\"lastname\": \"Bunny\"}'
But when I use the -File option, it fails because the $JSONString inside the script is equal to '{"lastname"::
PowerShell -File "C:\work\LV Demo\http-patch.ps1" -ReqURL myRESTApi -JSONString '{\"lastname\": \"Bunny\"}'
I assume here are some problems with quotes, but I can't find the right way.
When you use the -File parameter of powershell.exe, the Windows PowerShell CLI, only double quotes (") are recognized as having syntactic function when calling from outside PowerShell, such as from cmd.exe:
Therefore, switch from ' to " (the embedded " chars. require escaping as \" (sic) either way):
:: From cmd.exe
PowerShell -File "C:\work\LV Demo\http-patch.ps1" -JSONString "{\"lastname\": \"Bunny\"}" -ReqURL myRESTApi
By contrast, when you use -Command, '...' strings are recognized, after unescaped " chars. - the only ones with syntactic function during initial command-line parsing - have been stripped, because the resulting tokens are then interpreted as PowerShell code.
For guidance on when to use -Command vs. -File and the differences in resulting parsing, see this answer.
From inside PowerShell, there's rarely a need to to call another PowerShell instance, given that .ps1 files can be invoked directly, in-process.
In cases where you do need to call the CLI from inside PowerShell - say you need to call the CLI of the other PowerShell edition, PowerShell (Core)'s pwsh.exe, from powershell.exe or vice versa - the best choice is to use a script block ({ ... }) (which works only when calling from inside PowerShell), because that:
allows you to focus solely on PowerShell's own quoting and escaping requirements.
supports receiving typed output (objects other than strings), behind-the-scenes XML-based serialization, albeit with limited type fidelity - see this answer.
# From PowerShell
# Note how the " chars. now need NO escaping.
# (If you were to use a "..." string, you'd escape them as `" or "")
PowerShell {
& C:\work\LV Demo\http-patch.ps1" -JSONString '{"lastname": "Bunny"}' -ReqURL myRESTApi
}
While you can call via individual arguments, analogous to how you must call from outside PowerShell, you'll not only lose the benefit of typed output, but you'll also run in a longstanding bug up to PowerShell 7.2.x that requires manual escaping of embedded " chars. as \ when calling external programs - see this answer - as evidenced by one of your own attempts (here, using '...' is perfectly fine, because PowerShell recognizes it):
# !! Note the unexpected need to \-escape the embedded " chars.
PowerShell -File .\http-patch.ps1 -JSONString '{\"lastname\": \"Bunny\"}' -ReqURL myRESTApi
# Variant with double quotes.
# !! Note the *double* escaping: first with ` - for the sake of PowerShell itself -
# !! then with \ due to the bug.
PowerShell -File .\http-patch.ps1 -JSONString "{\`"lastname\`": \`"Bunny\`"}" -ReqURL myRESTApi

Execute a PowerShell script within RunAs in a script

I have a need to run a PowerShell script as another user (the users will actually be doing the auth) based on detected environment. The script leverages a smartcard for login. The problem I have is when the PowerShell.exe instance launches from the runas, it simply prints my command and doesn't actually run it.
Note: The filepaths have spaces that get escaped with double-`, just not shown here hence the escaped ``' in the actual command. Have also used ```".
Example:
Start-Process runas.exe "/netonly /smartcard /user:domain\$env:USERNAME ""powershell.exe -noexit -Command `'&$filePath -arg1 -arg2`' "" "
or
Start-Process runas.exe "/netonly /smartcard /user:domain\$env:USERNAME ""powershell.exe -noexit -File `'$filePath -arg1 -arg2`' "" "
File path points to the same .ps1 file. The dir is similar to: C:\Users\Username\My Documents\script.ps1
When this runs I simply get a script window that doesn't actually run, it just prints the File name. I have also tried using -File but that simply crashes (regardless of -noexit). The runas bit works fine, I get my smartcard prompt etc its just the actual PowerShell launch that I am struggling with.
These work just fine calling them directly from PowerShell Cli but when in a .ps1 it just won't work.
Any help would be greatly appreciate.
There's generally no reason to use Start-Process to run a console application such as runas.exe - unless you explicitly want the application to run in a new window, asynchronously (by default) - see this answer for more information.
Eliminating Start-Process simplifies the quoting:
runas.exe /netonly /smartcard /user:domain\$env:USERNAME "powershell.exe -noexit -File \`"$filePath\`" -arg1 -arg2"
Note the - unfortunate - need to manually \-escape the embedded " chars. (`"), which is required up to at least v7.1 - see this answer.
As for what you tried:
On Windows, passing a '...'-enclosed string to the PowerShell CLI's -Command (-c) parameter from outside PowerShell (such as via runas.exe) causes it to be interpreted as a verbatim PowerShell string, printing its content as-is (except for whitespace normalization).
You can verify this by running the following from cmd.exe, for instance:
C:\>powershell -c 'Get-Date; whatever I type here is used as-is - almost'
Get-Date; whatever I type here is used as-is - almost
Note how the multiple spaces before the word almost were normalized to a single one.
The reason is that on Windows only double-quoting ("...") has syntactic function on the command line - ' characters are interpreted verbatim and therefore passed through.
Therefore, when the -Command (-c) argument sees its argument(s) -- after command-line parsing - and then interprets the resulting space-joined, possibly double-quote-stripped arguments as PowerShell source code, a span of '...' is interpreted as a verbatim PowerShell string, as usual (see the conceptual about_Quoting_Rules help topic, which discusses the types of PowerShell string literals).
In concrete terms:
'Get-Date; whatever I type here is used as-is - almost' is parsed by the PowerShell CLI as the following arguments(!):
'Get-Date;, whatever, I, type, here, is, used, as-is, -, almost' - note how any information about the amount of whitespace that originally separated these argument is lost in the process.
These arguments are then joined with a single space to form the string that PowerShell then interprets as PowerShell source code, which yields:
'Get-Date; whatever I type here is used as-is - almost'
This amounts to a verbatim (single-quoted) PowerShell string literal, whose content is then output as-is.

Powershell inline commands on command line, quotation marks and new line

I have a powershell script that takes a parameter from the command line.
The passed parameter is a string with new line characters but I cannot get it to work as I wish.
Example running from command line:
powershell.exe -command "& {& 'C:\My Scripts\Script.ps1' "Some Text`nWoo Hoo"}"
The script above is only passed the text Some. If I use single quotes around the passed text the `n newline character is interpreted literally, not as a newline.
I'm sure there is some way to solve this!
thanks
Since your overall command string is (of necessity when calling from cmd.exe) double-quoted, you must escape the embedded double quotes as \" (even though inside PowerShell it is ` that serves as the escape character, as used in the embedded `n escape sequence):
# From cmd.exe
powershell.exe -command "& 'C:\My Scripts\Script.ps1' \"Some Text`nWoo Hoo\""
Also note that there's never a need to use -Command "& { ... }" when calling the PowerShell CLI - just -Command "..." is sufficient.
In your case, embedded double-quoting (\"...\"), i.e. use of an (escaped) expandable string is a must in order to get interpolation of the `n escape sequence to an actual newline.
In cases where a verbatim (literal) string is sufficient, you can bypass the need to escape by using single-quoting ('...').
See the bottom section of this answer for more information about string literals in PowerShell.

Powershell v2: Replace CRLF with LF

Using Powershell v2 called from a batch file, I want to replace each CRLF in a file with just an LF. If a file only has LF without any CR, then I want all the LF to be left alone.
I do not want a terminating CRLF in the resultant file, if possible.
I found this question here on Stack Overflow, that seems to be a close match, but it does not specify a Powershell version requirement, nor does it specify the other criteria above. Hence this question.
The accepted answer for that question recommends this code:
$in = "C:\Users\abc\Desktop\File\abc.txt"
$out = "C:\Users\abc\Desktop\File\abc-out.txt"
(Get-Content $in) -join "`n" > $out
I slightly modified it, and adjusted it to work from within a batch file, to read:
powershell -Command "(Get-Content file1.txt) -join '`n' > file2.txt"
Unfortunately, this does not work. All LF's are converted to the string `n.
How can I get this to work?
Those before me are right you should use "`n"
When using PowerShell I recommend executing it the following switches:
-noninteractive indicate you do not want to interact with the powershell
-NoProfile - speeds up the things considerably (skips loading profile)
-ExecutionPolicy Bypass - bypasses security issues if you are on companies environment
Edit:
Sorry about the mistake you mentioned. I now have PowerShell 2.0 testing facility.
The fixed your example (the mistake was that you have to escape the double quotes due to the powershell.exe interpreting them). This approach does not work completely as it leaves CRLF at the end of the file:
powershell.exe -noninteractive -NoProfile -ExecutionPolicy Bypass -Command "& {(Get-Content file_crlf.txt) -join \"`n\" > file_lfonly.txt};"
However, the completely correct solution needs different approach (via IO.file class):
powershell.exe -noninteractive -NoProfile -ExecutionPolicy Bypass -Command "& {[IO.File]::WriteAllText('file_lfonly.txt', ([IO.File]::ReadAllText('file_crlf.txt') -replace \"`r`n\", \"`n\"))};"
This completely converts your CRLF to LF. Just small piece of warning it converts to ASCII not Unicode (out of scope of this question).
All examples are now tested on PowerShell v2.0.50727.
A couple of things:
Single quotes ' are literal - use double quotes " so that PowerShell knows you mean new line
If you need to escape double quotes within double quotes, use "" or `"
Edit:
Your original post worked for me, so looks like this is related to PowerShell 5 v 2, and I am unable to test the solution. Instead of escape characters, here is a solution using scriptblock:
powershell -Command {(Get-Content file1.txt) -join "`n" > file2.txt}
It's worth noting that this is trivial in Notepad++ and can be done for multiple files at once:

Executing powershell command under cmd and INFO: Could not find files for the given pattern(s)

I am trying to execute the following powershell commands from CMD, for example:
powershell Get-WmiObject Win32_PnPSignedDriver
powershell Get-WmiObject Win32_PnPSignedDriver > test.txt
which both work correctly.
But when I do a query, for example:
powershell (Get-WmiObject Win32_PnPSignedDriver | where {$_.location -like "*PCI bus 0, device 22, function 0*"}).DeviceName
I am getting this message the cause of which I can not pin down:
INFO: Could not find files for the given pattern(s)
This seems to work for me (I was seeing the same error as you originally):
powershell -command "(Get-WmiObject Win32_PnPSignedDriver | where {$_.location -like '*PCI bus 0, device 22, function 0*'}).DeviceName"
To complement the existing answers with general guidelines for passing commands to powershell.exe from cmd.exe (a Command Prompt):
Enclose the entire PowerShell command in "..." (double quotes).
This protects its contents from unwanted up-front interpretation by cmd.exe - as happened with | in this case, as explained in aschipfl's answer.
However, cmd.exe-style environment-variable references (e.g., %USERNAME%) are still expanded.
For quoting that is embedded in the PowerShell command:
Use '...' (single quotes) where feasible; this is what Mark Wragg's helpful answer does, but it is only an option if the quoted string is meant to be a literal (doesn't contain variable references or subexpressions).
If you do need embedded "...", escape it as \"...\"
Note that while inside PowerShell it is the ` (backtick) that serves as the escape character, when passing strings from the outside PowerShell requires \.
Simplified examples (run from cmd.exe):
Note that passing a command without a parameter name implies the -Command parameter; run powershell -? to see the command-line syntax.
You may also want to use -NoProfile so that the user profile isn't loaded every time.
Commands that don't need embedded quoting - simply double-quote:
powershell -noprofile "get-date"
powershell -noprofile "(get-date).Date"
powershell -noprofile "get-date | get-member"
Commands with embedded quoting - use '...' or \"...\":
powershell -noprofile "(get-date).Year -like '*17'"
powershell -noprofile "$v=17; (get-date).Year -like \"*$v\""
Commands that incorporate the value of a cmd.exe environment variable:
:: # Nothing special to do: cmd.exe expands the reference irrespective of quoting.
set "v=17"
powershell -noprofile "(get-date).Year -like '*%v%'"
:: # More robust variant that passes the value as an argument to a script block.
set "v=17"
powershell -noprofile ". { param($v) (get-date).Year -like \"*$v\" }" "%v%"
Optional reading: calling powershell from POSIX-like shells such as bash:
The above rules apply analogously, except that you'll typically use '...' (single quotes) to enclose the entire PowerShell command, which categorically prevents up-front interpretation by the shell.
Using "..." is an option if up-front expansions of shell-variable references and command substitutions are explicitly desired, but the potential for confusion is great, because both POSIX-like shells and PowerShell use sigil $ to refer to variables - it may not be obvious what is expanded when.
POSIX-like shells categorically do not support embedding ' instances in side '...' strings, which necessitates a somewhat awkward workaround (see below).
Simplified examples (run from a POSIX-like shell such as bash):
Commands that don't need embedded quoting - simply single-quote:
powershell -noprofile 'get-date'
powershell -noprofile '(get-date).Date'
powershell -noprofile 'get-date | get-member'
Commands with embedded quoting - replace embedded ' instances with '\'' (sic) and use embedded " as-is:
powershell -noprofile '(get-date).Year -like '\''*17'\'''
powershell -noprofile '$v=17; (get-date).Year -like "*$v"'
Commands that incorporate the value of a shell/environment variable:
# By using a double-quoted string, the shell expands $v up front.
v=17
powershell -noprofile "(get-date).Year -like '*$v'"
# It gets trickier if you want to reference PS variables too.
# Note how the PS variable's $ is \-escaped so that the shell doesn't interpret it.
v=$HOME
powershell -noprofile "\$HOME -eq '$v'"
# More robust variant that passes the value as an argument to a script block.
v=17
powershell -noprofile '. { param($v) (get-date).Year -like "*$v" }' "$v"
I am pretty sure that the pipe character | is the problem here, because cmd tries to process it.
Simply escape it by preceding with ^:
powershell (Get-WmiObject Win32_PnPSignedDriver ^| where {$_.location -like "*PCI bus 0, device 22, function 0*"}).DeviceName
If this code is used within a parenthesised block of code in cmd, you may need to escape the closing ) as well (the opening ( can be escaped too, but there is no need):
powershell ^(Get-WmiObject Win32_PnPSignedDriver ^| where {$_.location -like "*PCI bus 0, device 22, function 0*"}^).DeviceName