How PowerShell does decide which mode when parsing? - powershell

I can't quite understand how PowerShell parses commands and need your help.
I read the following explanation by Microsoft's about_parsing documentation:
When processing a command, the PowerShell parser operates in expression mode or in argument mode:
In expression mode, character string values must be contained in quotation marks. Numbers not enclosed in quotation marks are treated as numerical values (rather than as a series of characters).
In argument mode, each value is treated as an expandable string unless it begins with one of the following special characters: dollar sign ($), at sign (#), single quotation mark ('), double quotation mark ("), or an opening parenthesis (().
If preceded by one of these characters, the value is treated as a value expression.
I can understand when parsing a command, PowerShell uses either expression mode or argument mode, but I can't quite understand the following examples.
$a = 2+2
Write-Output $a #4(int), expression mode
Write-Output $a/H #4/H(str), argument mode
I wonder PowerShell expands variable first and then decide which mode when parsing, but is it right?
If so, there's another question about data type.
It seems reasonable for me the former command produces integral, but the latter one doesn't. Why can integer 4 be put next to string /H?
I tried this example and it worked. It seems variables turn into string whatever data type they are when expanded. Is it right?
$b = 100
Add-Content C:\Users\Owner\Desktop\$b\test.txt 'test'
I appreciate for your help.
Edited to clarify the point after got the comment
I've got the comment that the both Write-Output examples are argument mode, so can the examples be interpreted like this?
Write-Output "$a"
Write-Output "$a/H"
I'm terribly sorry for too ambiguous question, but I want to know:
In argument mode, double quotations are omitted?
The Write-Output examples are quoted from microsoft's document I linked and it says the first example produces integral, but is it wrong?

Related

`doskey /macros:all` produces quoted string I can't get rid of

First, I'm in PowerShell and I've entered the doskey /exename=powershell.exe option.
Second, I did something that I now realize doesn't quite work:
doskey envpath=$env:Path -split ';'
The goal was to have it print the path environment variable (whatever it is at the time I later enter envpath). However, it seems to have evaluated $env:Path while defining the macro, so the macro now appears to be all the paths in my path environment variable followed by '-split ;'. So that's a problem, but only listed here for context. I'll figure that out separately. The purpose of this question (one question per post) is the following:
I was following this and getting something weird...
If I now enter doskey /macros:all I get:
"envpath=C:\WINDOWS\system32;C:\WINDOWS;<etc>;" -split ;
Please note the quotes.
Now, if, per the above-linked other answer, I enter doskey envpath=something (literally) then doskey /macros:all returns:
"envpath=C:\WINDOWS\system32;C:\WINDOWS;<etc>;" -split ;
envpath=something
(which is expected except for the quoted part).
And when I do doskey envpath= it clears/deletes that macro, and doskey /macros:all, returns the first result again.
So my question: What is this entry in the quotes and how do I get rid of that please?
Hopefully I've explained that clearly enough. If confused please feel free to ask for clarification. Thanks in advance for help!
As noted in the answer to your related question,
it's best to avoid use of doskey.exe in PowerShell, because getting it to work requires forgoing PowerShell's own, rich command-line editing experience, by unloading the PSReadLine module first (Remove-Module PSReadLine) - see this answer for background information.
the better alternative is to define a PowerShell function and add it to your $PROFILE file, as shown in the linked answer.
If you want to use doskey.exe nonetheless, define your macro as follows (for PowerShell (Core) 7+, replace powershell.exe with pwsh.exe):
doskey /exename=powershell.exe envpath = `$env:Path -split "';'"
The tokens that make up the PowerShell command must be passed as individual arguments to doskey.exe, and be sure to follow the = with a space.
If you accidentally pass the PowerShell command as a single, quoted argument, doskey.exe stores enclosing "..." as part of the macro and includes these quotes when it expands the macro.
If you additionally also include the macro name in that single, quoted argument, you not only get a virtually unusable macro,[1] you also cannot remove it in-session (neither individually, with doskey /exename=powershell.exe envpath=, nor as part of clearing all macros with Alt-F10) - you must start a new PowerShell session to get rid of it.
Note that the macro name is also included if you attempt partial quoting in PowerShell, e.g., doskey envpath="`$env:Path -split ','" is effectively the same as doskey "envpath=`$env:Path -split ','", due to how PowerShell rebuilds the command line behind the scenes (see below).
To avoid instant expansion of $env:Path, the $ character is preceded by PowerShell's escape character, the so-called backtick (`).
To preserve the '...'-quoting around ;, outer "..." quoting is used.
This is necessary, because PowerShell rebuilds the command line behind the scenes when it invokes external programs, which involves translating '...' quoting into "..." quoting if necessary; that is, irrespective of what quoting was originally used on the PowerShell side, an argument is enclosed in "..." if it contains spaces and used unquoted otherwise; thus, ';' by itself would turn into just ; on the behind-the-scenes command line; an originally partially quoted argument that contains spaces ends up being double-quoted as a whole.
[1] In a macro definition that doskey /macros or doskey /macros:all lists as "envpath=C:\WINDOWS\system32;C:\WINDOWS;<etc>;" -split ;, the macro name is "envpath verbatim, i.e. including the opening ". The - then unbalanced - closing " is retained in the text to expanded to.

Powershell escaping quotation marks in a variable that is used in another variable

I've found plenty of post explaining how to literally escape both single and double quotation marks using either """" for one double quotation mark, or '''' for a single quotation mark (or just doing `"). I find myself in a situation where I need to search through a list of names that is input in a different query:
Foreach($Username in $AllMyUsers.Username){
$Filter = "User = '$Username'"
# do some more code here using $Filter
}
The problem occurs when I reach a username like O'toole or O'brian which contains quotation marks. If the string is literal I could escape it with
O`'toole
O''brian
etc.
But, since it's in a loop I need to escape the quotation mark for each user.
I tried to use [regex]::Escape() but that doesn't escape quotation marks.
I could probably do something like $Username.Replace("'","''") but it feels like there should be a more generic solution than having to manually escape the quotation marks. In other circumstances I might need to escape both single and double, and just tacking on .Replace like so $VariableName.Replace("'","''").Replace('"','""') doesn't feel like it's the most efficient way to code.
Any help is appreciated!
EDIT: This feels exactly like a "how can I avoid SQL injection?" question but for handling strings in Powershell. I guess I'm looking for something like mysqli_real_escape_string but for Powershell.
I could probably do something like $Username.Replace("'","''") but it feels like there should be a more generic solution than having to manually escape the quotation marks
As implied by Mathias R. Jessen's comment, there is no generic solution, given that you're dealing with an embedded '...' string, and the escaping requirements entirely depend on the ultimate target API or utility - which is unlikely to be PowerShell itself (where ' inside a '...' string must be doubled, i.e. represented as '').
In the case at hand, where you're passing the filter string to a System.Data.DataTable's .DataView's .RowFilter property, '' is required as well.
The conceptually cleanest way to handle the required escaping is to use -f, the format operator, combined with a separate string-replacement operation:
$Filter = "User = '{0}'" -f ($UserName -replace "'", "''")
Note how PowerShell's -replace operator - rather than the .NET [string] type's .Replace() method - is used to perform the replacement.
Aside from being more PowerShell-idiomatic (with respect to: syntax, being case-insensitive by default, accepting arrays as input, converting to strings on demand), -replace is regex-based, which also makes performing multiple replacements easier.
To demonstrate with your hypothetical .Replace("'","''").Replace('"','""') example:
#'
I'm "fine".
'# -replace '[''"]', '$0$0'
Output:
I''m ""fine"".

How can I still get each line of a text file when the variable is in quotations?

I've been messing arround with Powershell and googling various things as I go along. This one is a little hard to put into words that google woule understand. I can get the indevidual lines of a text file in powershell by indexing:
$textFile = Get-Content "myText.txt"
$textFile[0]
This would output the first line of the text file. But when I put the text file in quotes it will output all lines, even with the index
"$textFile[0]"
How can I still get only get the line I want, while wrapping the variable in quotes? If I try "$textFile"[0] it will just give me the whole file as before. The reason I'm trying to do this is because I'm trying to make that one line of the text file part of a bigger string that I can execute
$remote = "Enter-PSSession -ComputerName`", textFile[0]"
Invoke-Expression $remote
This is my way of illustrating what I'm trying to do.
You can use any of the following methods:
# Sub-expression operator
"Some Text $($textFile[0])"
# String format operator
"My Text {0}" -f $textFile[0]
# Concatenation
("Text"+$textFile[0])
Surrounding double quotes tells PowerShell to expand the string inside. Any variables within will be interpolated. Variables begin with $ and their following names can only have certain characters without requiring a special escape. [ would require an escape and since it isn't escaped, PowerShell interprets the variable name ending with the character just before the [. Therefore $textFile is interpolated, the whole file contents are converted into a string, and [0] is appended to the end of the string.
You can see details of the operators at About_Operators.
See About_Variables for how to create a variable including cases with special characters even if that doesn't directly apply here.

Powershell - Replacing substrings with wildcards

I am writing a function in powershell, and part of it needs to replace occurrences of substrings with a wildcard. Strings will look something like this:
"something-#{reference}-somethingElse-#{anotherReference}-01"
I want it to end up looking like this:
"something-*-somethingElse-*-01"
The problem I have here is that I don't know what "#{something}" will be, just that there will be multiple substrings enclosed inside a hashtag followed by curly braces. I've tried the Replace method like so:
$newString = $originalString.Replace('#{*}', '*')
I was hoping that would replace everything from the hashtag to the ending curly brace, but it doesn't work like that. I'm trying to avoid cumbersome code that is based on finding the indices of '#' and '}' and then replacing, and hoping there is a simpler and more elegant solution.
Your replace has at least one problem, possibly two;
the method $string.Replace() is from the .Net framework string class - it's PowerShell, but it's exactly what you'd get in C#, minimal PowerShell script-y convenience added on top - and it's for literal text replacements - it doesn't support wildcards or regular expressions.
The 'wildcard' support in PowerShell is quite limited, to the -like operator only, as far as I know. That can't do text replacing, and it's a convenience for people who don't know regular expression; behind the scenes it converts to a regular expression anyway. So the dream of a a*b replace won't work either.
As #PetSerAl comments, regular expressions and the PowerShell -replace operator are the PowerShell way to do every string pattern replace quickly and without .indexOf().
Their pattern #{[^}]*} expands to:
#{} on the outside, as literal characters
[^}] as a character class saying "not a } character, but anything else"
[*}]* - as many not }'s as there are.
So, match hash and open brace, everything that isn't the closing brace brace (to avoid overrunning past the closing brace), then the closing brace. Replace it all with literal *.
Implicitly, do that search/replace as many times as possible in the input string.

Arguments in Powershell are not evaluated correctly

Arguments in powershell don't get evaluated as I expect them.
A mathematical expression as argument is casted to a string if the parameter type is object or string.
Powershell code: write-host 1+1
Result printed on screen: 1+1
Expected result: 2
Could somebody tell me why does this happen?
What is the logic behind this behavior?
In the language specification it clearly states that if the parameter is of type object then the value will be passed as is with no casting.
Could you please point me to the language specification that describes this behavior as I could not find it.
Note: any function that accepts an object or a string will suffer from the same behavior.
this link here
When processing a command, the Windows PowerShell parser operates
in expression mode or in argument mode:
- In expression mode, character string values must be contained in
quotation marks. Numbers not enclosed in quotation marks are treated
as numerical values (rather than as a series of characters).
- In argument mode, each value is treated as an expandable string
unless it begins with one of the following special characters: dollar
sign ($), at sign (#), single quotation mark ('), double quotation
mark ("), or an opening parenthesis (().
Example Mode Result
------------------ ---------- ----------------
2+2 Expression 4 (integer)
Write-Output 2+2 Argument "2+2" (string)
Write-Output (2+2) Expression 4 (integer)
$a = 2+2 Expression $a = 4 (integer)
Write-Output $a Expression 4 (integer)
Write-Output $a/H Argument "4/H" (string)