Powershell escape character ` (backtick) not escaping string - powershell

I'm having trouble adding a new line into a string in powershell:
Get-ChildItem "C:\Users\matt\Desktop\CShell Install" |foreach {'<Component Id="'+$_.name+'" Guid="' +[guid]::NewGuid() + '">`r`n<File Id="'+$_.name+'" Source="$(var.CShell.TargetPath)"></File></Component>'}
As you can see I want a newline to occur at
``r`n
instead they are printed literally.
Any pointers?

Do not use Single Quote, where you want PowerShell to honor backticks (or any other characters that you need PowerShell to interpret).
"FirstLine`r`nSecondLine" prints
FirstLine
SecondLine
'"FirstLine`r`nSecondLine"' prints
"FirstLine`r`nSecondLine"

Related

How to make ($Line_1`n$Line_2) work in a CMD script?

I use the following code in a CMD script file
PowerShell Add-Type -AssemblyName System.Windows.Forms;^
$Line_1 = 'Hello!';^
$Line_2 = 'How are you?';^
[System.Windows.Forms.MessageBox]::Show($Line_1)
The above will show only ($Line_1)
If ($Line_1`n$Line_2) is used, nothing will be shown.
How do I make it show both $Line_1 and $Line_2?
The simplest solution is (note the \"$Line_1`n$Line_2\" part):
PowerShell -c Add-Type -AssemblyName System.Windows.Forms; ^
$Line_1 = 'Hello!'; ^
$Line_2 = 'How are you?'; ^
[System.Windows.Forms.MessageBox]::Show(\"$Line_1`n$Line_2\")
Note that I've explicitly added the -c (-Command) parameter name to signal that a PowerShell command string is being passed. While that isn't necessary in Windows PowerShell, which defaults to -Command, it is in PowerShell (Core) 7+, where -File is now the default - see the CLI documentation for Windows PowerShell and PowerShell (Core) 7+.
That is, you must use $Line_1`n$Line_2 inside "...", an expandable string, and you must \-escape the " characters so that PowerShell doesn't strip them as part of its command-line parsing (in the absence of overall double-quoting, """ works too).
Unfortunately, the parsing rules change when for /f is used in order to process PowerShell's output line by line and/or capture it in a variable:
Note: The following uses [Console]::WriteLine() so as to produce console output, just for the sake of using similar syntax to the [System.Windows.Forms.MessageBox]::Show() method call while allowing something to be captured by for /f. In real life there is no good reason to call [Console]::WriteLine().
for /f "delims=" %%l in ('
PowerShell -c Add-Type -AssemblyName System.Windows.Forms^; ^
$Line_1 ^= 'Hello!'^; ^
$Line_2 ^= 'How are you?'^; ^
[Console]::WriteLine^(\"$Line_1`n$Line_2\"^)
') do echo [%%l]
= , ; ( ) must additionally be escaped (outside what cmd.exe sees as a "..." string).
If you additionally enclose a \"...\" string in "..." to prevent whitespace normalization (see next section) you must ^-escaped the enclosing (outer) "; e.g.,
^"\"Marley & Me\"^"
The line continuations (^ at the end of command-interior lines) are actually optional inside for /f, but they were included for consistency.
Summary of quoting and escaping requirements:
Your multi-line technique with line continuations (^ at the end of lines) - which syntactically cannot use "..." quoting around the entire command, because cmd.exe doesn't support double-quoted multi-line strings - requires careful ^-escaping of all cmd.exe metacharacters that should be passed through to PowerShell, notably & | < > ^, and, additionally, if PowerShell is called from inside a for /f statement, , ; = ( ) - unless these characters happen to be part of a substring that cmd.exe sees as double-quoted; e.g., a & placed inside the \"...\" string - e.g. \"$Line_1`n & $Line_2\" - must not be ^-escaped (but see below re whitespace normalization).
As an exception, metacharacter % must always be escaped as %% (which only works in batch files, not at the command prompt - see this answer).
Additionally, if setlocal enabledelayedexpansion is in effect (or cmd.exe was started with /V:ON), ! must be escaped too, but inexplicably as follows:
As ^^! (sic) outside of what cmd.exe sees as a "..." string (where the other metacharacter require just one ^)
As ^! inside such a string (where the other metacharacters require no escaping).
When calling via for /f, line continuations are optional - that is, you may omit the ^ at the end of the command-interior lines.
Each statement must be ;-terminated (as you have done), because the line-continuation (^) results in no newline between the input lines, so PowerShell sees them as a single line on which multiple statements must be ;-separated.
Because neither cmd.exe nor PowerShell's initial command-line parsing knows about single-quoted strings ('...') and because the escaped " characters in \"...\" strings have no syntactic function during command-line parsing, such strings are broken into multiple arguments if they contain whitespace:
In effect, runs of multiple adjacent spaces inside such strings are normalized to a single space each. E.g., 'How are you?' and \"How are you?\" are ultimately seen as 'How are you?'and "How are you?" by PowerShell.
To avoid that, additionally enclose such strings in "..." :
Single-quoted PowerShell strings: "'How are you?'"
Note: This is not necessary if the entire command is enclosed in "..."
Double-quoted PowerShell strings: ^"\"How are you?\"^".
The ^-escaped enclosing " chars. ensure that cmd.exe still sees what is between the inner \"...\" as double-quoted (because it doesn't recognize \ as an escape char.), obviating the need to ^-escape cmd.exe metacharacters there.
Note: If the entire command is enclosed in "...", a different approach is required: use "... "^""How are you?"^"" ..." with powershell.exe (Windows PowerShell), and "... ""How are you?"" ..." with pwsh.exe (PowerShell (Core) 7+).
In case you want to include comments in the PowerShell code, you must use the form
^<# ... #^> - i.e. (escaped) inline comments - normal single-line comments (# ....) are not supported (because it would require a newline to terminate them, but there are no newlines between statements in the invocation method at hand).
How PowerShell parses the arguments passed to its CLI's -Command / -c parameter:
PowerShell gives you the choice between passing the command string
either: as a single argument, enclosed in overall "..."; e.g.:
powershell -c "Get-Date -Format 'yyyy MMM'"
or: multiple arguments - possibly individually "..."-quoted - which PowerShell then joins to form a single string; e.g.:
powershell -c Get-Date -Format "'yyyy MMM'"
In both cases, unescaped " characters are stripped from the argument(s), as they are assumed to have merely syntactic function for the sake of the command line rather than the resulting PowerShell command.
After stripping syntactic " characters and joining the resulting arguments with spaces, if applicable, Powershell interprets the resulting string as PowerShell code.
Note: This differs fundamentally from how arguments are parsed when you use the -File CLI parameter to invoke a script file and pass arguments to it - see this answer for more.
Obviously $Line_1 + "`n" + $Line_2 and "$Line_1`n$Line_2" works normally. It's just tricky to send the command string to PowerShell from cmd with its legacy quirks because:
In cmd () are special characters in various places denoting a block
The token delimiter is not only <space> and <tab> but also ; , = <0x0B> <0x0C> and <0xFF>. This changes tokenization behavior of cmd, but the command being called may reparse the command using its rules one more time
According to the documentation PowerShell expects the command in a single string in the last parameter (which isn't quite true since the document wasn't updated correctly), so you need to quote the whole thing or escape all the delimiters. The easiest solution is to use a single line and escape the quotes in "`n" like this
PowerShell "Add-Type -AssemblyName System.Windows.Forms; $Line_1 = 'Hello!'; $Line_2 = 'How are you?'; [System.Windows.Forms.MessageBox]::Show($Line_1 + \"`n\" + $Line_2)"
If you want to put the commands in multiple lines then you can't quote the string. To put the whole thing as a single argument now you need to escape all the spaces (somehow you don't need to escape ; in this case, possibly because after the command line is passed to PowerShell, it calls GetCommandLineW and parses again the whole thing itself)
PowerShell Add-Type^ -AssemblyName^ System.Windows.Forms;^
$Line_1^ =^ 'Hello!';^
$Line_2^ =^ 'How^ are^ you?';^
[Windows.Forms.MessageBox]::Show($Line_1^ +^ \"`n\"^ +^ $Line_2)"
Alternatively you can avoid that "`n" string by getting the new line directly with [char]10
PowerShell -Command Add-Type -AssemblyName System.Windows.Forms;^
$Line_1 = 'Hello!';^
$Line_2 = 'How are you?';^
[System.Windows.Forms.MessageBox]::Show($Line_1 + [char]10 + $Line_2)
Finally a solution that works without any escaping which utilizes the EncodedCommand option of PowerShell which receives the base64 encoded string of a UTF-16 command string. You can get the encoded version by running this in PowerShell
$str = #'
>> Add-Type -AssemblyName System.Windows.Forms;
>> $Line_1 = 'Hello!';
>> $Line_2 = 'How are you?';
>> [System.Windows.Forms.MessageBox]::Show($Line_1 + "`n" + $Line_2)
>> '#
PS C:\Users> [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($str))
QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7AAoAIAA9ACAAJwBIAGUAbABsAG8AIQAnADsACgAgAD0AIAAnAEgAbwB3ACAAYQByAGUAIAB5AG8AdQA/ACcAOwAKAFsAUwB5AHMAdABlAG0ALgBXAGkAbgBkAG8AdwBzAC4ARgBvAHIAbQBzAC4ATQBlAHMAcwBhAGcAZQBCAG8AeABdADoAOgBTAGgAbwB3ACgAIAArACAAIgAKACIAIAArACAAKQA=
After having the encoded version you can call this from cmd
PowerShell -EncodedCommand QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7AAoAJABMAGkAbgBlAF8AMQAgAD0AIAAnAEgAZQBsAGwAbwAhACcAOwAKACQATABpAG4AZQBfADIAIAA9ACAAJwBIAG8AdwAgAGEAcgBlACAAeQBvAHUAPwAnADsACgBbAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwAuAE0AZQBzAHMAYQBnAGUAQgBvAHgAXQA6ADoAUwBoAG8AdwAoACQATABpAG4AZQBfADEAIAArACAAIgBgAG4AIgAgACsAIAAkAEwAaQBuAGUAXwAyACkA
There are multiple ways to concatenate strings which you can look up, but probably the easiest way is to use the + symbol. `n is a newline character which will place Line 2 below line 1. Note that ` is a backtick symbol (usually on same key as the tilde ~)
[System.Windows.Forms.MessageBox]::Show($Line_1 + "`n" + $Line_2)
After looking again at your post I noticed you were close with ($Line_1`n$Line_2). You're only missing some double-quotes
[System.Windows.Forms.MessageBox]::Show("$Line_1`n$Line_2")
Powershell is happy to replace variables with their values when placed inside double-quotes. You can read more about expandable strings here and here

How to get around using apostrophe in double quotes with Powershell

I am working with Powershell. My issue is that my file path (which does not exist on a local computer) has an apostrophe in it. Powershell is seeing this as a single quote, so it is giving me the following error: The string is missing the terminator: '. I thought that I could escape the single quote using a backtick, but that gave me the same error.
The error does not occur when I am doing the first line of code, and I don't even need the backtick for that part. I can even see that the contents of the variable matches up with the file path that I am using. It is only when I am doing the invoke-expression part that it is giving me the error.
I am using https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-expression?view=powershell-7, so I don't think the second line of the code is the problem.
My code is listed below:
$code = "\\example\example\John_Doe`'s_Folder\example.ps1"
invoke-expression -command $code
I have also tried wrapping the entire file path in double-quotes and single-quotes, but my program did not like that either. I can't remove the apostrophe as we have over a hundred of systems that are directing to John_Doe's_Folder.
Invoke-Expression should generally be avoided; definitely don't use it to invoke a script or external program.
In your case, simply use &, the call operator to invoke your script via the path stored in variable $code (see this answer for background information), in which case the embedded ' needs no escaping at all:
$code = "\\example\example\John_Doe's_Folder\example.ps1"
& $code
As for what you tried:
"\\example\example\John_Doe`'s_Folder\example.ps1" turns into the following verbatim string content:
\\example\example\John_Doe's_Folder\example.ps1
That is, the ` was removed by PowerShell's parsing of the "..." string literal itself, inside of which ` acts as the escape character; since escape sequence `' has no special meaning, the ` is simply removed.
For the ` to "survive", you need to escape the ` char. itself, which you can do with ``:
"\\example\example\John_Doe``'s_Folder\example.ps1"

Using PowerShell to replace string that contains double quotes in the string [duplicate]

I have a DOS batch file that has a line that executes a powershell script. First I tried a very simple script with this line in the batch file:
powershell -command "get-date" < nul
That worked great. But the script has nested double-quote characters, which can sometimes be escaped with a backtick (`) character. So then I tried this:
powershell -command "Write-Host `"hello world`"" < nul
That also worked great. However, the script I need to run is pretty complicated and has more than one level of nested double-quote characters. I have taken the complicated script and simplified it to an example that has the same principles here:
[string]$Source = " `"hello world`" ";
Write-Host $Source;
If I save this script inside a PS script file and run it, it works fine, printing out “hello world” including the double quotes, but I need to embed it in the line in the batch file. So I take the script and put it all on one line, and try to insert it into the batch file line, but it doesn’t work. I try to escape the double-quotes, but it still doesn’t work, like this:
powershell -command "[string]$Source = `" `"hello world`" `";Write-Host $Source;" < nul
Is there a way to do what I want? You might ask why I am doing this, but it’s a long story, so I won’t go into the details.
thanks
You'll have to use a combination of batch's escape character and PowerShell's escape character.
In batch, when escaping quotes, you use the common shell backslash (\) to escape those quotes. In Powershell, you use the backtick `.
So if you wanted to use batch to print out a quoted string with Powershell, you need to first batch escape the quote to declare the variable in Powershell, then to ensure the string is quoted you need batch and Powershell escape another quote, and then your add your desired string, ensuring you batch escape first.
For your example, this will work:
powershell -command "[string]$Source = \"`\"hello world`\"\"; Write-Host $Source;"
Here's a break down of the declaration of the $Source variable:
"[string]$Source = # open quote to begin -command parameter declaration
\" # batch escape to begin the string portion
`\" # Powershell+Batch escape
hello world # Your content
`\" # Posh+Batch again
\"; # Close out the batch and continue
more commands " # Close quote on -command parameter
This renders the string like this in batch:
`"hello world`"
One note, you don't need to explicitly cast $Source as a string since you are building it as a literal string from scratch.
$Source = "string stuff" will work as intended.

Replace special character with double double quotes

I have a text file with the some special character $, which needs to be replaced by double double quotes. I am using a bat file in which I invoke powershell.exe and write the replace command. Below is the command:
powershell "gc C:\Temp\Test.csv| foreach-object {$_ -replace '$','""""""'}|sc C:\Temp\Test_Replace.csv"
I know that double quotes are escaped by a double quote so """"" is equivalent to "". But as seen in the above code I need to write 6 double quotes to get the equivalent 2 quotes. I cannot figure out the reason for this.
Can Someone please illustrate the point I am missing.
As I said in comments, I think it is a but in PowerShell.exe command line parser. When it see "" inside quoted context, it not only produce literal " but also close quoted context:
CMD> powershell '"1 2""3 4"'
1 2"3 4
As you can see, there is only one space between 3 in 4 in printed string. You need to put extra double quote to reopen quoted context:
CMD> powershell '"1 2"""3 4"'
1 2"3 4
So, in fact, you have to triplicate double quote to produce just one literal double quote character.
You just need to escape the nested double quote properly. Also, the ForEach-Object isn't required. Put the Get-Content in an expression (i.e. in parentheses) and you can use the -replace operator directly.
powershell -Command "(gc C:\Temp\in.csv) -replace '$','\"\"'|sc C:\Temp\out.csv"
If you want to replace literal $ characters instead of adding double quotes to the end of each line you need to escape the $ as well, as Mathias pointed out:
powershell -Command "(gc C:\Temp\in.csv) -replace '\$','\"\"'|sc C:\Temp\out.csv"

Searching for backslash in a string with Powershell

I need to search for the amount of backslashes in a string to determine some file path parameters. I have not worked out a way to seach for a backslash without Powershell thinking it is an escapee character.
([regex]::Matches($FilePath, "\" )).count
Or
$a -match "\"
These both come up with an error "Illegal \ at end of pattern"
Thanks!
You can escape a backslash with a backslash:
[Regex]::Matches($FilePath, "\\").Count