Difference between () and $() [duplicate] - powershell

This question already has answers here:
What is the difference between $a = $(Read-Host) and $a = (Read-Host)?
(2 answers)
Closed 5 years ago.
What's the difference between
Write-Host (Get-Date) # just paren
and
Write-Host $(Get-Date) # dollar-paren
Content within the parens could be anything, just going with a simple example. Is there any difference between the two?
I consider myself reasonably experienced with PS, but it's these little things that bug me, especially during code review and the like. Has anyone come across a good source for "here is how the language works" with enough detail to derive answers to these sorts of questions?

The sub expression ($(...)) contains a StatementBlockAst. It can take any number of statements, meaning keywords (if, foreach, etc), pipelines, commands, etc. Parsing is similar to the inside of a named block like begin/process/end.
The paren expression ((...)) can contain a single ExpressionAst which is a limited subset of the AST. The most notable difference between a statement and an expression is that keywords are not parsed.
$(if ($true) { 'It worked!' })
# It worked!
(if ($true) { 'It worked!' })
# if : The term 'if' is not recognized as the name of a cmdlet, function,
# script file, or operable program. Check the spelling of the name, or
# if a path was included, verify that the path is correct and try again.
# At line:1 char:2
# + (if ($true) { 'It worked' })
# + ~~
# + CategoryInfo : ObjectNotFound: (if:String) [], CommandNotFoundException
# + FullyQualifiedErrorId : CommandNotFoundException
Also as others have noted, the sub expression will expand in double quoted strings.

The () helps with order of operations
The $() helps with evaluating values inside of the ()
For instance, if you're trying to find today's date in a string you could do the following:
echo "The length of Bryce is (Get-Date)"
echo "The length of Bryce is $(Get-Date)"
You'll see that the output is different (in one it gives you literally "(Get-Date)" whereas in the other it gives you the evaluated expression of Get-Date)
You can read more about syntax operators here

Parenthesis are used to group and establish order just as they do in mathematics. Starting in PowerShell v3 you can also use them to evaluate a property of a group, such as getting the file names for the files in the current folder by running:
(Get-ChildItem).Name
A sub-expression $() evaluates the script within it, and then presents the output of that to be used in the command. Often times used within strings to expand a property of an object such as:
"Hello $($User.Name), would you like to play a game?"
It can also be useful when working with ComObjects, such as Excel where you may have a range that you want to test against a property of each item. While this does not work because the Range object does not have a Font property:
$Range.Font|Where{$_.Bold}
This would work, because it would output the Range as a collection of Cell objects, each of which have a Font property:
$($Range).Font|Where{$_.Bold}
You can think of sub-expressions as being a script within your script, since they can be several commands long, and the entire thing is evaluated at once so that the end output can be used for the parent command.

Related

Why are (return) and return different?

I was testing a function and was playing around with what I could do with return and stumbled accross a strange issue, in PowerShell 5.1 and PwSh 7.1, the return cmdlet seemd to not work in a group:
PS C:\Users\Neko> return "test"
test
PS C:\Users\Neko> (return "test")
return : The term 'return' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct
and try again.
At line:1 char:2
+ (return "test")
+ ~~~~~~
+ CategoryInfo : ObjectNotFound: (return:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
If I put return in a group, it seemed to become unrecognized by PowerShell.
I understand that return prints to the console and does not have "output" per say like Write-Host,
PS C:\Users\Neko> $test = return "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko> $test = Write-Host "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko>
but I can't fathom why having return in a grouping expression is causing such an issue.
Why is using return in groups causing a strange error and how can I remedy that?
This doesn't occur in subexpressions,
PS C:\Users\Neko> $(return "test")
test
#Neko Musume -- as per your request.
Because as per the MS Docs on the topic ...
About_Return | MS Docs
... it can only be used as defined. Meaning 'Exit the current scope, which can be a function, script, or script block.', thus using parens, as you are trying to do, does not meet that criteria.
return is a statement, whereas only commands and expressions are allowed inside (), the grouping operator.
All keyword-based constructs are statements; to name a few: other flow-control statements (exit, throw, ...) and loops and conditionals (if, while, switch, ...) - see about_Language_Keywords for the list of all keywords in PowerShell.
In order to group (embed in another statement) a statement (potentially even multiple ones, separated with ;, as usual) - you need $(), the subexpression operator.
Note that the operator's name is somewhat unfortunate; "sub-statement operator" would have been more descriptive.
The primary use of $(...) is inside expandable strings ("..."), and it is typically not needed elsewhere; instead, (...) should be used when possible, so as to avoid potential side effects (and prevent visual clutter) - see this answer.
That said, in this specific case, as the about_Return help topic states, return's purpose is to exit the enclosing scope (function, script, or script block), so using it as the RHS of a variable assignment never makes sense.
The reason for the can't-use-a-statement-as-an-expression restriction is rooted in the fundamentals of PowerShell's grammar, as summarized by a member of the PowerShell team in this GitHub issue (emphasis added):
PowerShell has pipelines contained by statements, not statements contained by pipelines. That's always been the case, and the way to put a statement within a pipeline is to use the $(...) syntax — that's its only purpose to exist outside of strings in the language.
The two most important consequences to end users are:
You cannot use a statement in a pipeline:
That is, something like foreach ($i in 1..3) { $i } | % { $_ + 1 } does not work.[1]
While you could use $(...) as a workaround($(foreach ($i in 1..3) { $i }) | % { $_ + 1 }, doing so forgoes the streaming benefits of the pipeline; that is, the output from the foreach loop is collected in memory first, in full before the results are sent through the pipeline.
In order to get the usual streaming behavior (one-by-one passing of output through the pipeline), enclose the loop in a script block ({ ... }) and invoke it with &, the call operator[2]:
& { foreach ($i in 1..3) { $i } } | % { $_ + 1 }
(Using expressions in a pipeline works, just fine, however: 1..3 | % { $_ + 1 })
You cannot use the flow-control statements return, exit, and throw with && and ||, the pipeline-chain operators:
That is, something like ls nosuchfile || exit 1 does not work.
Instead, you must use $(...): ls nosuchfile || $(exit 1)
Unfortunately, these restrictions cannot be lifted if backward compatibility must be maintained; quoting again from the previously linked GitHub issue:
[Lifting these restrictions amounts to] a huge change in PowerShell's grammar (effectively creating a fork in the language)
While PowerShell to date (as of v7.0) has had an ironclad commitment to backward compatibility which prevented serious breaking changes, a debate about whether to permit such changed and how to (carefully) manage them has now begun: see this GitHub issue.
If such changes are eventually permitted, many longstanding annoyances could finally be tackled - see this GitHub issue.
[1] Note that foreach is also a built-in alias for the ForEach-Object cmdlet, but the command shown uses the foreach loop statement - see about_ForEach.
[2] If the statement has side effects that you want the caller to see, use ., the dot-sourcing operator instead of &.
Note: This answer complements my (already lengthy) other one in order to address some of your analysis in the question:
I understand that return prints to the console and does not have "output" per say like Write-Host
It is Write-Host that has no output in the data sense: that is, it does not write to the success output stream (stream number 1 - see about_Redirection)
Instead, it writes to the host (the display; in PSv5+, it does so via stream 6, the information stream), bypassing the success output stream, preventing capturing or redirection of the output - see this answer for background information, including when use of Write-Host (rather than Write-Output / implicit output) is appropriate.
By contrast, return does send the output from its (optional) operand[1] to the success output stream.
As for the surprising behavior of:
PS> $test = return "test"
test
You've hit an edge case (discussed in this since-closed GitHub issue): something that is syntactically allowed, but doesn't make sense to do: the return statement effectively short-circuits the assignment: that is, "test" is sent directly to the success output stream (1), and the assignment is ignored, because the flow-control statement return exits the enclosing scope - not just the statement! - before the assignment occurs.
You can verify that return indeed still targets the success output stream as follows:
PS> $testOuter = & { $test = return "test" }; "[$testOuter]"
[test] # proof that $testOuter was able to capture the `return` value.
However, it's easy to avoid this problem: any expression or command (pipeline) can directly be used as the RHS.
# Do NOT use `return` in the RHS of an assignment.
PS> $test = "test"; "[$test]"
[test]
[1] While using return with an expression (such as return $foo) is probably most common, note that it supports an entire pipeline as its operand (e.g., return Get-Date | % Year).

What type of object is $<drivename>: (such as `$code:`) in Powershell?

I was using tab autocompletion for a variable name in Powershell 5.1 today and noticed that one of the choices was the name of a PSDrive. The drive name is docs and I wanted to expand is called $document_name. When I typed $do<tab>, the shell did indeed expand what I had typed to $document_name but for some reason, I typed <tab> a second time and that's when the expanded text changed to $docs:.
I explored further and found that this type of variable exists for each of my PSDrives, or at least tab expansion suggests that it does.
More formally, for every PSDrive PSD, tab expansion believes that $PSD: is a valid thing.
My question is simple: what the heck are these? Here are some observations I've made so far:
These names are prefixed with $, so they look like PS variables. For the rest of this discussion (and in the earlier discussion above), I will assume they are variables and refer to them as such.
Although they appear to be variables, they are not listed in the Variable: PSDrive like most variables. In this way, it behaves like the $env "variable," which also is not listed in Variable:. I have a feeling if I could find documentation about $env, then I'd understand these objects also.
In some ways, they behave like pointers to filesystem objects. For example, if there is a file name readme.txt containing the text "Hello, world!" on a PSDrive named code, then all of the following are possible interactions with Powershell.
Fetch the contents of the file.
λ ${code:\readme.txt}
Hello, world!
Just to prove that the type of the above result is String:
λ ${code:\readme.txt} | % { $_.GetType().Name }
String
Trying to use this as a reference to the PSDrive doesn't work well for many operations, such as cd:
C:\
λ cd ${code:}
At line:1 char:4
+ cd ${code:}
+ ~~~~~~~~
Variable reference is not valid. The variable name is missing.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : InvalidBracedVariableReference
I could go on, but I'm stumped. If I pass $code: (or $env:, for that matter) to Get-Member, I get an error saying Variable reference is not valid.
So just what the heck are "variables" like $env and $<PSDrive>: (such as $code:)? Are they expressions? Built-in expressions? Some kind of object? Thanks for any help.
What you're seeing is namespace variable notation, which is a variable-based way to access the content of items in PowerShell drives whose underlying provider implements content-based access (i.e., implements the IContentCmdletProvider interface).
Terminology and documentation note:
As of this writing, the docs briefly explain namespace variable notation in the conceptual about_Scopes help topic, albeit without using that term and, somewhat confusingly, discuss it in the context of scope modifiers; while namespace qualifiers (such as $env:) are unrelated to scope modifiers (such as $script:), they use the same basic syntax form, however.[1]
The general syntax is:
${<drive>:<path>} # same as: Get-Content <drive>:<path>
${<drive>:<path>} = ... # same as: Set-Content <drive>:<path> -Value ...
The enclosing {...} aren't necessary if both the <drive> name and the <path> can syntactically serve as a variable name; e.g.:
$env:HOME # no {...} needed
${env:ProgramFiles(x86)} # {...} needed due to "(" and ")"
In practice, as of Windows PowerShell v5.1, the following in-box drive providers support namespace variable notation:
Environment (drive Env:)
Function (drive Function:)
Alias (drive Alias:)
FileSystem (drives C:, ...)
Variable (drive Variable:) - though virtually pointless, given that omitting the drive part accesses variables by default (e.g., $variable:HOME is the same as just $HOME).
Of these, the Env: drive is by far the most frequently used with namespace variable notation, even though most users aren't aware of what underlies an environment-variable references such as $env:HOME.
On occasion you see it used with a filesystem drive - e.g., ${c:\foo\file.txt} - but the fact that you can only use literal paths and that you cannot control the character encoding limits its usefulness.
It allows interesting uses, however; e.g.:
PS> $alias:foreach # Get the definition of alias 'foreach'
ForEach-Object
PS> $function:prompt # Get the body of the 'prompt' function
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
# .Link
# https://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml
# Define a function foo that echoes 'hi' and invoke it.
PS> $function:foo = { 'hi' }; foo
hi
Note:
Because ${<drive>:<path>} and ${<drive>:<path>} = <value> are equivalent to Get-Content -Path <drive>:<path> and Set-Content -Path <drive>:<path> <value>, paths are interpreted as wildcard expressions (because that's what -Path does, as opposed to -LiteralPath), which can cause problems with paths that look like wildcards - see this answer for an example and a workaround.
[1] Previously, the feature wasn't documented at all; GitHub docs issue #3343 led to the current documentation, albeit not in the way that said issue proposed.
$env is the Windows environment variables, the same as what you get when you do SET in a command prompt. There are a few that are PS-specific.
The variable is providing access to the Environment Provider. https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-6
There are a bunch of other Providers that are described here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_providers?view=powershell-6
As it says in the doco:
The model for data presentation is a file system drive. To use data
that the provider exposes, you view it, move through it, and change it
as though it were data on a hard drive. Therefore, the most important
information about a provider is the name of the drive that it
supports.

Powershell indexing for creating user logon name

I'm trying to make a user creation script for my company to make things more automated.
I want the script to take the Firstname + Lastname[0] to make the users logon name, but i can't get the syntax right,
I have tried writing {} and () but no luck there.
that's the original peace from my script
New-ADuser...........-UserPrincipalName $fname+$lname[0]
any tips?
Gabriel Luci's helpful answer provides an effective solution and helpful pointers, but it's worth digging deeper:
Your problem is that you're trying to pass expression $fname+$lname[0] as an argument, which requires enclosing (...):
New-ADuser ... -UserPrincipalName ($fname+$lname[0])
PowerShell has two distinct parsing modes, and when a command (such as New-ADUser) is called, PowerShell operates in argument mode, as opposed to expression mode.
Enclosing an argument in (...) forces a new parsing context, which in the case of $fname+$lname[0] causes it to be parsed in expression mode, which performs the desired string concatenation.
In argument mode, unquoted arguments are implicitly treated as if they were enclosed in "...", i.e., as expandable strings under the following circumstances:
If they don't start with (, #, $( or #(.
If they either do not start with a variable reference (e.g., $var) or do start with one, but are followed by other characters that are considered part of the same argument (e.g., $var+$otherVar).
Therefore, $fname+$lname[0] is evaluated as if "$fname+$lname[0]" had been passed:
The + become part of the resulting string.
Additionally, given that inside "..." you can only use variable references by themselves (e.g., $fname), not expressions (e.g., $lname[0]), $lname[0] won't work as intended either, because the [0] part is simply treated as a literal.
Embedding an expression (or a command or even multiple expressions or commands) in "..." requires enclosing it in $(...), the subexpression operator, as in Gabriel's answer.
For an overview of PowerShell's string expansion rules, see this answer.
The following examples use the Write-Output cmdlet to illustrate the different behaviors:
$fname = 'Jane'
$lname = 'Doe', 'Smith'
# WRONG: Same as: "$fname+$lname[0]", which
# * retains the "+"
# * expands array $lname to a space-separated list
# * treats "[0]" as a literal
PS> Write-Output -InputObject $fname+$lname[0]
Jane+Doe Smith[0]
# OK: Use an expression via (...)
PS> Write-Output -InputObject ($fname+$lname[0])
JaneDoe
# OK: Use an (implicit or explicit) expandable string.
PS> Write-Output -InputObject $fname$($lname[0]) # or: "$fname$($lname[0])"
JaneDoe
# OK: Use an intermediate variable:
PS> $userName = $fname + $lname[0]; Write-Output -InputObject $userName
Use a string for the UserPrincipalName, with the variables in the string:
New-ADuser -UserPrincipalName "$fname$($lname[0])"
PowerShell can usually figure out when you put a variable inside a string. When it can't, like in the case of $lname[0], you enclose it in $().
This is called "variable expansion" (other languages, like C#, call it "string interpolation"). Here's a good article that describes it in more detail: https://powershellexplained.com/2017-01-13-powershell-variable-substitution-in-strings/
i just saw the answers and a minute before i realized that i should actually set it up as another variable, $logon = $fname+lname[0]
and pass it as -userPrincipalName $logon.
Thanks for the help, you guy are the best!

$openfiledialog1 and Set-Content or Out-File how to do that? Passing a file-path argument composed of multiple variable and property references [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
SOLUTION on the bottom .
I find my self how to use
($openfiledialog1.ShowDialog() -eq 'OK')
{
$textboxFile.Text = $openfiledialog1.FileName
}
I can extract and change the text inside of this file and save at the same path.
I would like to save in another place and I tried some thing
(Get-Content $textboxFile.Text).Replace('$Name', 'test') |
Out-File $HOME\$openfiledialog1.SafeFileNames
I tried with Set-Content same issue, the script can't save and error appear.
ERROR: Set-Content : Could not open the alternate data stream ' Title: , FileName: C:\Users\Administrator.SERVERAD\Documents\test.txt' of the file
ERROR: 'C:\Users\Administrator.SERVERADSystem.Windows.Forms.OpenFileDialog'. MainForm.psf (24, 59):
ERROR: At Line: 24 char: 59
ERROR: + ... xt).replace('$Name', 'test')|Set-Content -Path $HOME\$openfiledialog1
The final usage is change some data inside of HTML file.
Ansgar I don't understand your solution but you keep me on the good way thank you so much.
After 2 hours and some break I find a SOLUTION, but I don't why I can't use directly $openfiledialog1.SafeFileName.
$buttonBrowse_Click = {
if ($openfiledialog1.ShowDialog() -eq 'OK') {
$textboxFile.Text = $openfiledialog1.SafeFileName
}
}
$button1_Click = {
$b = $textboxFile.Text
# also this things work too $b=$openfiledialog1.SafeFileName
# TODO: Place custom script here
(Get-Content $openfiledialog1.FileName).replace('$Name', "text") |
Out-File $HOME\$b
}
tl;dr
In order to reference property value $openfiledialog1.SafeFileName as part of the file-path argument, you must enclose it in $(...):
... | Out-File "$HOME\$($openfiledialog1.SafeFileName)" # Note the $(...)
You could omit the enclosing ", but I suggest using them for conceptual clarity and general robustness.
As for what you tried:
$HOME\$openfiledialog1.SafeFileNames isn't expanded the way you expect:
.SafeFileNames is treated as a literal, meaning that $openfiledialog1 by itself is expanded, which is not your intent.
Here's a simple example:
PS> Write-Output $HOME\$PSVersionTable.PSVersion
C:\Users\jdoe\System.Management.Automation.PSVersionHashTable.PSVersion
You may have expected something like C:\Users\jdoe\5.1.16299.492, but as you can see, .PSVersion was retained as a literal, and $PSVersionTable by itself was stringified, which resulted in the (unhelpful) System.Management.Automation.PSVersionHashTable representation.
The reason for this behavior is that an unquoted token such as $HOME\$openfiledialog1.SafeFileName is treated much like an expandable string, i.e. like a token enclosed in "...".
Inside "...", only mere variable references can be embedded as-is (e.g., "Value: $var"); by contrast, accessing a variable's property is an expression that must be enclosed in $(...), i.e. a call to the subexpression operator (e.g., "Value: $($var.Foo)")
For an overview of string-expansion rules, see this answer of mine.
For an overview of how unquoted tokens are parsed as command arguments, see this answer of mine.

PowerShell String Formatting: Why is the colon character causing my variable's value to be blank?

I was looking over what's coming with the next WinRM and PowerShell 3 and I was looking through the list of breaking changes and saw something that I'd never seen before.
The example was:
$server = "msp42"
$status = "online"
"$server: $status"
The resulting output was:
online
OK, I'd never encountered that before and have no clue why the colon caused an issue.
A solution suggested in the document was to put a space (which is silly because then you change the output):
"$server : $status"
Another suggestion was to use this format (new to me!):
"${server}: $status"
The final suggestion was to make an expression, which I am familiar with and use all the time:
"$($server): $status"
So, my questions to you PowerShell gurus out there are:
What the heck is up with that colon? Does it do something?
What the heck is the ${variable} syntax? Is it strictly to deal with the colon or does it have some neat features?
The colon is a valid character for variable names, e.g. in $Env:PATH, etc.
You can use the following option, too
$server`: $status
or, for some cases a format string is more readable:
'{0}: {1}' -f $server, $status
Back to the colon. There is a special case for variable names that correspond to an item on a PSDrive:
$Env:Foo # equivalent to the contents of Env:\Foo
$Function:C: # equivalent to the contents of Function:\C:
${C:\autoexec.bat} # ... you get the picture
The syntax ${} exists to be able to specify variable names that otherwise use characters reserved for other parts of the syntax. You could see it as being similar (but more powerful) to C#'s # in front of identifiers. See above where a \ is used in the variable name, since $Drive:Item only works for the current container on a drive (or the root for non-hierarchic ones like Env, Alias or Function).
Another example where the variable name would be normally invalid syntax:
PS> $+
The term '$+' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ $+
+ ~~
+ CategoryInfo : ObjectNotFound: ($+:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS> ${+} = 5
PS> ${+}
5
PS> Get-Variable +
Name Value
---- -----
+ 5