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!
Related
I'm trying to get the following command into a program I am developing in PowerShell studio, I'm having some issues figuring out the escaping sequence here, and testing takes forever since I have to import the PST just to view the data :(
Consider the following command which works correctly from the shell...
New-MailboxExportRequest -Mailbox user#domain.com -Name JobName -IncludeFolders "#Inbox#/*","#SentItems#" -ContentFilter {(Received -gt "06/10/2022") -and (Sent -gt "06/10/2022")} -FilePath "\\Server\Folder\MyPST.pst"
I am using variables inside program designer, so my actual code looks like this...
New-MailboxExportRequest -Mailbox $mailbox -Name $jobname -IncludeFolders $IncludeFolders -ContentFilter {(Received -gt "$RecDate") -and (Sent -gt "$SentDate")} -FilePath "$FilePath"
Basically I need to get the first code sample working using variables, however the -IncludeFolders and -ContentFilter parameters require some escaping I can't seem to figure out. Any and all help is much appreciated.
tl;dr:
You need to enclose your -ContentFilter argument in "..." overall in order to support embedding variable values, which you can themselves enclose in embedded '...' quoting:
-ContentFilter "(Received -gt '$RecDate') -and (Sent -gt '$SentDate')"
No special syntax considerations apply with respect to using a variable with -IncludeFolders: Just make sure that variable $IncludeFolders contains an array of strings identifying the target folders; applied to your example:
$IncludeFolders = '#Inbox#/*', '#SentItems#'
If the list of folders must be parsed from user input provided as a single string ($textbox5.text in this example) do the following to convert this single string to an array of names:
# If $textbox5.text contains string '#Inbox#/*, #SentItems#',
# the result is the same as above.
$IncludeFolders = ($textbox5.text -split ',').Trim()
Read on for background information and caveats re -ContentFilter.
Note:
The -ContentFilter information below also applies to other Exchange cmdlets that accept filters, such as Get-Recipient with its -Filter parameter.
It also applies analogously to the -Filter parameter used with AD (Active Directory) cmdlets such as Get-ADUser, although the situation there is complicated by the fact that the AD provider does support evaluation of PowerShell variables, but only in simple cases - see this answer.
New-MailboxExportRequest's -ContentFilter expects a string argument, not a script block ({ ... }), which you've used in your question.
While you can situationally get away with a script block, it is best avoided for two reasons:
When a script block is used as a string (converted to one), string interpolation (embedding the values of variables in the string) is not supported - a script block stringifies to its verbatim content (sans { and }).
Conceptually, use of a script block can give the mistaken impression that an arbitrary piece of PowerShell code may be passed, which is not the case: -ContentFilter supports a domain-specific syntax only that only emulates a limited set of PowerShell features, called OPath filter syntax.
Notably, evaluation of PowerShell variables is not supported - their values must be embedded in the string up front, by PowerShell, using string interpolation.
Therefore:
It's best to use a string argument with -ContentFilter to begin with.
Since you do need to embed variable values, you need string interpolation, via an expandable (double-quoted) string ("..."), inside of which you may quote values with '...' for syntactical convenience.
In case there is no need to embed variable values, use a verbatim (single-quoted) string ('...'), inside of which you may quote values with "...", as in your question. (In effect, this is the equivalent of the (ill-advised) use of { ... }).
Applied to your example: The following embeds the (stringified) values of variables $RecDate and $SentDate in your -ContentFilter argument:
# Note the use of "..." for the outer quoting, and
# '...' for the embedded quoting.
-ContentFilter "(Received -gt '$RecDate') -and (Sent -gt '$SentDate')"
Caveats:
If the values of the variables to embed themselves contain ', use escaped embedded "..." quoting instead; e.g., `"$var`"
If a value could contain ' and/or " and you don't know which, you'd have to use an extra layer of up-front string interpolation on the PowerShell side to escape one of them, depending on the embedded quoting character chosen, using $(...), the subexpression operator, using a -replace operation:
In this escaping you must technically satisfy the OPath filter syntax requirements; the linked help OPath topic suggests that, like in PowerShell, '' can be used to escape ' inside a '...' string; e.g.: -ContentFilter "Body -like '$($var -replace "'", "''")*'"
While, as stated, OPath filters do not support evaluating PowerShell variable references in general, the following automatic variables - which are conceptually constants - are recognized (the linked help topic calls them system values):
$true, $false
$null
Therefore, if you use "..." for the outer quoting (for string interpolation), the $ in these variables must be escaped with ` (the so-called backtick, PowerShell's escape character), to prevent PowerShell from replacing these variable references with their values up front:
`$true, `$false
`$null
I have the following code:
$DatabaseSettings = #();
$NewDatabaseSetting = "" | select DatabaseName, DataFile, LogFile, LiveBackupPath;
$NewDatabaseSetting.DatabaseName = "LiveEmployees_PD";
$NewDatabaseSetting.DataFile = "LiveEmployees_PD_Data";
$NewDatabaseSetting.LogFile = "LiveEmployees_PD_Log";
$NewDatabaseSetting.LiveBackupPath = '\\LiveServer\LiveEmployeesBackups';
$DatabaseSettings += $NewDatabaseSetting;
When I try to use one of the properties in a string execute command:
& "$SQlBackupExePath\SQLBackupC.exe" -I $InstanceName -SQL `
"RESTORE DATABASE $DatabaseSettings[0].DatabaseName FROM DISK = '$tempPath\$LatestFullBackupFile' WITH NORECOVERY, REPLACE, MOVE '$DataFileName' TO '$DataFilegroupFolder\$DataFileName.mdf', MOVE '$LogFileName' TO '$LogFilegroupFolder\$LogFileName.ldf'"
It tries to just use the value of $DatabaseSettings rather than the value of $DatabaseSettings[0].DatabaseName, which is not valid.
My workaround is to have it copied into a new variable.
How can I access the object's property directly in a double-quoted string?
When you enclose a variable name in a double-quoted string it will be replaced by that variable's value:
$foo = 2
"$foo"
becomes
"2"
If you don't want that you have to use single quotes:
$foo = 2
'$foo'
However, if you want to access properties, or use indexes on variables in a double-quoted string, you have to enclose that subexpression in $():
$foo = 1,2,3
"$foo[1]" # yields "1 2 3[1]"
"$($foo[1])" # yields "2"
$bar = "abc"
"$bar.Length" # yields "abc.Length"
"$($bar.Length)" # yields "3"
PowerShell only expands variables in those cases, nothing more. To force evaluation of more complex expressions, including indexes, properties or even complete calculations, you have to enclose those in the subexpression operator $( ) which causes the expression inside to be evaluated and embedded in the string.
#Joey has the correct answer, but just to add a bit more as to why you need to force the evaluation with $():
Your example code contains an ambiguity that points to why the makers of PowerShell may have chosen to limit expansion to mere variable references and not support access to properties as well (as an aside: string expansion is done by calling the ToString() method on the object, which can explain some "odd" results).
Your example contained at the very end of the command line:
...\$LogFileName.ldf
If properties of objects were expanded by default, the above would resolve to
...\
since the object referenced by $LogFileName would not have a property called ldf, $null (or an empty string) would be substituted for the variable.
Documentation note: Get-Help about_Quoting_Rules covers string interpolation, but, as of PSv5, not in-depth.
To complement Joey's helpful answer with a pragmatic summary of PowerShell's string expansion (string interpolation in double-quoted strings ("...", a.k.a. expandable strings), including in double-quoted here-strings):
Only references such as $foo, $global:foo (or $script:foo, ...) and $env:PATH (environment variables) can directly be embedded in a "..." string - that is, only the variable reference itself, as a whole is expanded, irrespective of what follows.
E.g., "$HOME.foo" expands to something like C:\Users\jdoe.foo, because the .foo part was interpreted literally - not as a property access.
To disambiguate a variable name from subsequent characters in the string, enclose it in { and }; e.g., ${foo}.
This is especially important if the variable name is followed by a :, as PowerShell would otherwise consider everything between the $ and the : a scope specifier, typically causing the interpolation to fail; e.g., "$HOME: where the heart is." breaks, but "${HOME}: where the heart is." works as intended.
(Alternatively, `-escape the :: "$HOME`: where the heart is.", but that only works if the character following the variable name wouldn't then accidentally form an escape sequence with a preceding `, such as `b - see the conceptual about_Special_Characters help topic).
To treat a $ or a " as a literal, prefix it with escape char. ` (a backtick); e.g.:
"`$HOME's value: $HOME"
For anything else, including using array subscripts and accessing an object variable's properties, you must enclose the expression in $(...), the subexpression operator (e.g., "PS version: $($PSVersionTable.PSVersion)" or "1st el.: $($someArray[0])")
Using $(...) even allows you to embed the output from entire commands in double-quoted strings (e.g., "Today is $((Get-Date).ToString('d')).").
Interpolation results don't necessarily look the same as the default output format (what you'd see if you printed the variable / subexpression directly to the console, for instance, which involves the default formatter; see Get-Help about_format.ps1xml):
Collections, including arrays, are converted to strings by placing a single space between the string representations of the elements (by default; a different separator can be specified by setting preference variable $OFS, though that is rarely seen in practice) E.g., "array: $(#(1, 2, 3))" yields array: 1 2 3
Instances of any other type (including elements of collections that aren't themselves collections) are stringified by either calling the IFormattable.ToString() method with the invariant culture, if the instance's type supports the IFormattable interface[1], or by calling .psobject.ToString(), which in most cases simply invokes the underlying .NET type's .ToString() method[2], which may or may not give a meaningful representation: unless a (non-primitive) type has specifically overridden the .ToString() method, all you'll get is the full type name (e.g., "hashtable: $(#{ key = 'value' })" yields hashtable: System.Collections.Hashtable).
To get the same output as in the console, use a subexpression in which you pipe to Out-String and apply .Trim() to remove any leading and trailing empty lines, if desired; e.g.,
"hashtable:`n$((#{ key = 'value' } | Out-String).Trim())" yields:
hashtable:
Name Value
---- -----
key value
[1] This perhaps surprising behavior means that, for types that support culture-sensitive representations, $obj.ToString() yields a current-culture-appropriate representation, whereas "$obj" (string interpolation) always results in a culture-invariant representation - see this answer.
[2] Notable overrides:
• The previously discussed stringification of collections (space-separated list of elements rather than something like System.Object[]).
• The hashtable-like representation of [pscustomobject] instances (explained here) rather than the empty string.
#Joey has a good answer. There is another way with a more .NET look with a String.Format equivalent, I prefer it when accessing properties on objects:
Things about a car:
$properties = #{ 'color'='red'; 'type'='sedan'; 'package'='fully loaded'; }
Create an object:
$car = New-Object -typename psobject -Property $properties
Interpolate a string:
"The {0} car is a nice {1} that is {2}" -f $car.color, $car.type, $car.package
Outputs:
# The red car is a nice sedan that is fully loaded
If you want to use properties within quotes follow as below. You have to use $ outside of the bracket to print property.
$($variable.property)
Example:
$uninstall= Get-WmiObject -ClassName Win32_Product |
Where-Object {$_.Name -like "Google Chrome"
Output:
IdentifyingNumber : {57CF5E58-9311-303D-9241-8CB73E340963}
Name : Google Chrome
Vendor : Google LLC
Version : 95.0.4638.54
Caption : Google Chrome
If you want only name property then do as below:
"$($uninstall.name) Found and triggered uninstall"
Output:
Google Chrome Found and triggered uninstall
In the code below, why does the [int] type assignment for name1 get ignored and is actually treated as part of the value? Bug?
$name1 = 4
$name2 = 4
$name3 = 4
$myObject = New-Object System.Object
$myObject | Add-Member -type NoteProperty -name name1 -Value [int]$name1
$myObject | Add-Member -type NoteProperty -name name2 -Value $($name2 -as [int])
$myObject | Add-Member -type NoteProperty -name name3 -Value $([int]$name3)
$myObject
Output:
name1 name2 name3
----- ----- -----
[int]4 4 4
Powershell version:
get-host | select-object version
Version
-------
5.1.19041.1023
There's good information in the existing answers, but let me attempt a systematic overview:
tl;dr
In order to pass the output from an expression (e.g. [int] $name1), or (nested) command (e.g. Get-Date -Format yyyy) as an argument to a command (e.g. Add-Member), enclose it in (...), the grouping operator:
Add-Member -type NoteProperty -name name1 -Value ([int] $name1)
By contrast, $(...), the subexpression operator is typically not needed in this scenario, and its use can have side effects - see this answer.
In short: (Outside of expandable strings, "..."), you only ever need $(...) to either enclose a language statement (such as an if statement or foreach loop) or multiple statements (any mix of commands, expressions, and language statements separated with ;).
PowerShell's parsing modes:
PowerShell has two fundamental parsing modes:
argument mode, which works like shells.
In argument mode, the first token is interpreted as a command name (the name of cmdlet, function, alias, or the name of path of an external executable or .ps1 script), followed by a whitespace-separated list of arguments, where strings may be unquoted[1] and arguments composed of a mix of literal parts and variable references are typically treated like expandable strings (as if they were enclosed in "...").
expression mode, which works like programming languages, where strings must be quoted, and operators and language statements such as assignments, foreach and while loops, casts can be used.
The conceptual about_Parsing provides an introduction to these modes; in short, it is the first token in a given context that determines which mode is applied.
A given statement may be composed of parts that are parsed in either mode, which is indeed what happens above:
Because your statement starts with a command name (Add-Member), it is parsed in argument mode.
(...) forces a new parsing context, which in the case at hand ([int] $name1) is parsed in expression mode, due to starting with [).
What is considered a metacharacter (a character with special, syntactic meaning) differs between the parsing modes:
[ and = are special only in expression mode, not in argument mode, where they are used verbatim.
Conversely, a token-initial # followed by a variable name is only special in argument mode, where it is used for parameter splatting.
Compound argument [int]$name1 is therefore treated as if it were an expandable string, and results in verbatim string [int]4.
Some expressions do not require enclosing in (...) when used as command arguments (assume $var = 'Foo'):
A stand-alone variable reference (e.g. Write-Output $var or Write-Output $env:OS)
Property access on such a reference (e.g. Write-Output $var.Length)
Method calls on such a reference (e.g. Write-Output $var.ToUpper())
Note that these arguments are passed with their original data type, not stringified (although stringification may be performed by the receiving command).
Pitfalls:
You sometimes need to use "..." explicitly in order to suppress property-access interpretation and have a . following a variable reference be interpreted verbatim (e.g. Write-Output "$var.txt" in order to get verbatim foo.txt).
If you use $(...) as part of a compound argument without explicit "..." quoting, that argument is broken into multiple arguments if the $(...) subexpression starts the argument (e.g., Write-Output $('a' + 'b')/c passes two arguments, verbatim ab and /c, whereas Write-Output c/$('a' + 'b') passed just one, verbatim c/ab).
Similarly, mixing quoted and unquoted strings to form a single argument only works if the argument starts with an unquoted token (e.g., Write-Output One"$var"'$Two' works as expected and yields verbatim OneFoo$Two, but Write-Output 'One'"$var"'$Two' is passed as three arguments, verbatim One, Foo, and $Two).
In short:
The exact rules for how arguments are parsed are complex:
This answer summarizes the rules for unquoted arguments.
This answer summarizes mixing quoted and unquoted strings in a single argument
This answer) (bottom section) summarizes PowerShell's string literals in general.
To be safe, avoid use of $(...) outside "..." and avoid mixing quoting styles in a single string argument; either use a (single) "..." string (e.g. Write-Output "$(Split-Path $PROFILE)/foo.txt" or ) or string concatenation in an expression (Write-Output ('One' + $var + '$Two')
[1] Assuming they contain neither spaces nor any of PowerShell's metacharacters (see this answer). While quoting typically takes the form of enclosing the entire argument in single or double-quotes, as appropriate (e.g. 'foo bar', "foo $var"), it is also possible to quote (escape) individual characters (e.g. foo` bar), using the backtick (`), PowerShell's escape character.
From the about_Parsing help file:
Argument mode
When parsing, PowerShell first looks to interpret input as an expression. But when a command invocation is encountered,
parsing continues in argument mode.
Argument mode is designed for parsing arguments and parameters for
commands in a shell environment. All input is treated as an expandable
string unless it uses one of the following syntaxes:
...
You can enclose the code in a subexpression ($(...)) to avoid PowerShell treating [int]$name1 as an expandable string, as you've already found :-)
This is by design. You need to wrap in parentheses in order to evaluate the expression before passing it into the function.
Its the same if you try to put a function call there for instance:
$myObject | Add-Member -type NoteProperty -name name1 -Value Write-Host
returns as:
name1
-----
Write-Host
And Write-Host [string]2 returns [string]2 not 2
I have the following code:
$DatabaseSettings = #();
$NewDatabaseSetting = "" | select DatabaseName, DataFile, LogFile, LiveBackupPath;
$NewDatabaseSetting.DatabaseName = "LiveEmployees_PD";
$NewDatabaseSetting.DataFile = "LiveEmployees_PD_Data";
$NewDatabaseSetting.LogFile = "LiveEmployees_PD_Log";
$NewDatabaseSetting.LiveBackupPath = '\\LiveServer\LiveEmployeesBackups';
$DatabaseSettings += $NewDatabaseSetting;
When I try to use one of the properties in a string execute command:
& "$SQlBackupExePath\SQLBackupC.exe" -I $InstanceName -SQL `
"RESTORE DATABASE $DatabaseSettings[0].DatabaseName FROM DISK = '$tempPath\$LatestFullBackupFile' WITH NORECOVERY, REPLACE, MOVE '$DataFileName' TO '$DataFilegroupFolder\$DataFileName.mdf', MOVE '$LogFileName' TO '$LogFilegroupFolder\$LogFileName.ldf'"
It tries to just use the value of $DatabaseSettings rather than the value of $DatabaseSettings[0].DatabaseName, which is not valid.
My workaround is to have it copied into a new variable.
How can I access the object's property directly in a double-quoted string?
When you enclose a variable name in a double-quoted string it will be replaced by that variable's value:
$foo = 2
"$foo"
becomes
"2"
If you don't want that you have to use single quotes:
$foo = 2
'$foo'
However, if you want to access properties, or use indexes on variables in a double-quoted string, you have to enclose that subexpression in $():
$foo = 1,2,3
"$foo[1]" # yields "1 2 3[1]"
"$($foo[1])" # yields "2"
$bar = "abc"
"$bar.Length" # yields "abc.Length"
"$($bar.Length)" # yields "3"
PowerShell only expands variables in those cases, nothing more. To force evaluation of more complex expressions, including indexes, properties or even complete calculations, you have to enclose those in the subexpression operator $( ) which causes the expression inside to be evaluated and embedded in the string.
#Joey has the correct answer, but just to add a bit more as to why you need to force the evaluation with $():
Your example code contains an ambiguity that points to why the makers of PowerShell may have chosen to limit expansion to mere variable references and not support access to properties as well (as an aside: string expansion is done by calling the ToString() method on the object, which can explain some "odd" results).
Your example contained at the very end of the command line:
...\$LogFileName.ldf
If properties of objects were expanded by default, the above would resolve to
...\
since the object referenced by $LogFileName would not have a property called ldf, $null (or an empty string) would be substituted for the variable.
Documentation note: Get-Help about_Quoting_Rules covers string interpolation, but, as of PSv5, not in-depth.
To complement Joey's helpful answer with a pragmatic summary of PowerShell's string expansion (string interpolation in double-quoted strings ("...", a.k.a. expandable strings), including in double-quoted here-strings):
Only references such as $foo, $global:foo (or $script:foo, ...) and $env:PATH (environment variables) can directly be embedded in a "..." string - that is, only the variable reference itself, as a whole is expanded, irrespective of what follows.
E.g., "$HOME.foo" expands to something like C:\Users\jdoe.foo, because the .foo part was interpreted literally - not as a property access.
To disambiguate a variable name from subsequent characters in the string, enclose it in { and }; e.g., ${foo}.
This is especially important if the variable name is followed by a :, as PowerShell would otherwise consider everything between the $ and the : a scope specifier, typically causing the interpolation to fail; e.g., "$HOME: where the heart is." breaks, but "${HOME}: where the heart is." works as intended.
(Alternatively, `-escape the :: "$HOME`: where the heart is.", but that only works if the character following the variable name wouldn't then accidentally form an escape sequence with a preceding `, such as `b - see the conceptual about_Special_Characters help topic).
To treat a $ or a " as a literal, prefix it with escape char. ` (a backtick); e.g.:
"`$HOME's value: $HOME"
For anything else, including using array subscripts and accessing an object variable's properties, you must enclose the expression in $(...), the subexpression operator (e.g., "PS version: $($PSVersionTable.PSVersion)" or "1st el.: $($someArray[0])")
Using $(...) even allows you to embed the output from entire commands in double-quoted strings (e.g., "Today is $((Get-Date).ToString('d')).").
Interpolation results don't necessarily look the same as the default output format (what you'd see if you printed the variable / subexpression directly to the console, for instance, which involves the default formatter; see Get-Help about_format.ps1xml):
Collections, including arrays, are converted to strings by placing a single space between the string representations of the elements (by default; a different separator can be specified by setting preference variable $OFS, though that is rarely seen in practice) E.g., "array: $(#(1, 2, 3))" yields array: 1 2 3
Instances of any other type (including elements of collections that aren't themselves collections) are stringified by either calling the IFormattable.ToString() method with the invariant culture, if the instance's type supports the IFormattable interface[1], or by calling .psobject.ToString(), which in most cases simply invokes the underlying .NET type's .ToString() method[2], which may or may not give a meaningful representation: unless a (non-primitive) type has specifically overridden the .ToString() method, all you'll get is the full type name (e.g., "hashtable: $(#{ key = 'value' })" yields hashtable: System.Collections.Hashtable).
To get the same output as in the console, use a subexpression in which you pipe to Out-String and apply .Trim() to remove any leading and trailing empty lines, if desired; e.g.,
"hashtable:`n$((#{ key = 'value' } | Out-String).Trim())" yields:
hashtable:
Name Value
---- -----
key value
[1] This perhaps surprising behavior means that, for types that support culture-sensitive representations, $obj.ToString() yields a current-culture-appropriate representation, whereas "$obj" (string interpolation) always results in a culture-invariant representation - see this answer.
[2] Notable overrides:
• The previously discussed stringification of collections (space-separated list of elements rather than something like System.Object[]).
• The hashtable-like representation of [pscustomobject] instances (explained here) rather than the empty string.
#Joey has a good answer. There is another way with a more .NET look with a String.Format equivalent, I prefer it when accessing properties on objects:
Things about a car:
$properties = #{ 'color'='red'; 'type'='sedan'; 'package'='fully loaded'; }
Create an object:
$car = New-Object -typename psobject -Property $properties
Interpolate a string:
"The {0} car is a nice {1} that is {2}" -f $car.color, $car.type, $car.package
Outputs:
# The red car is a nice sedan that is fully loaded
If you want to use properties within quotes follow as below. You have to use $ outside of the bracket to print property.
$($variable.property)
Example:
$uninstall= Get-WmiObject -ClassName Win32_Product |
Where-Object {$_.Name -like "Google Chrome"
Output:
IdentifyingNumber : {57CF5E58-9311-303D-9241-8CB73E340963}
Name : Google Chrome
Vendor : Google LLC
Version : 95.0.4638.54
Caption : Google Chrome
If you want only name property then do as below:
"$($uninstall.name) Found and triggered uninstall"
Output:
Google Chrome Found and triggered uninstall
I have the following code:
$DatabaseSettings = #();
$NewDatabaseSetting = "" | select DatabaseName, DataFile, LogFile, LiveBackupPath;
$NewDatabaseSetting.DatabaseName = "LiveEmployees_PD";
$NewDatabaseSetting.DataFile = "LiveEmployees_PD_Data";
$NewDatabaseSetting.LogFile = "LiveEmployees_PD_Log";
$NewDatabaseSetting.LiveBackupPath = '\\LiveServer\LiveEmployeesBackups';
$DatabaseSettings += $NewDatabaseSetting;
When I try to use one of the properties in a string execute command:
& "$SQlBackupExePath\SQLBackupC.exe" -I $InstanceName -SQL `
"RESTORE DATABASE $DatabaseSettings[0].DatabaseName FROM DISK = '$tempPath\$LatestFullBackupFile' WITH NORECOVERY, REPLACE, MOVE '$DataFileName' TO '$DataFilegroupFolder\$DataFileName.mdf', MOVE '$LogFileName' TO '$LogFilegroupFolder\$LogFileName.ldf'"
It tries to just use the value of $DatabaseSettings rather than the value of $DatabaseSettings[0].DatabaseName, which is not valid.
My workaround is to have it copied into a new variable.
How can I access the object's property directly in a double-quoted string?
When you enclose a variable name in a double-quoted string it will be replaced by that variable's value:
$foo = 2
"$foo"
becomes
"2"
If you don't want that you have to use single quotes:
$foo = 2
'$foo'
However, if you want to access properties, or use indexes on variables in a double-quoted string, you have to enclose that subexpression in $():
$foo = 1,2,3
"$foo[1]" # yields "1 2 3[1]"
"$($foo[1])" # yields "2"
$bar = "abc"
"$bar.Length" # yields "abc.Length"
"$($bar.Length)" # yields "3"
PowerShell only expands variables in those cases, nothing more. To force evaluation of more complex expressions, including indexes, properties or even complete calculations, you have to enclose those in the subexpression operator $( ) which causes the expression inside to be evaluated and embedded in the string.
#Joey has the correct answer, but just to add a bit more as to why you need to force the evaluation with $():
Your example code contains an ambiguity that points to why the makers of PowerShell may have chosen to limit expansion to mere variable references and not support access to properties as well (as an aside: string expansion is done by calling the ToString() method on the object, which can explain some "odd" results).
Your example contained at the very end of the command line:
...\$LogFileName.ldf
If properties of objects were expanded by default, the above would resolve to
...\
since the object referenced by $LogFileName would not have a property called ldf, $null (or an empty string) would be substituted for the variable.
Documentation note: Get-Help about_Quoting_Rules covers string interpolation, but, as of PSv5, not in-depth.
To complement Joey's helpful answer with a pragmatic summary of PowerShell's string expansion (string interpolation in double-quoted strings ("...", a.k.a. expandable strings), including in double-quoted here-strings):
Only references such as $foo, $global:foo (or $script:foo, ...) and $env:PATH (environment variables) can directly be embedded in a "..." string - that is, only the variable reference itself, as a whole is expanded, irrespective of what follows.
E.g., "$HOME.foo" expands to something like C:\Users\jdoe.foo, because the .foo part was interpreted literally - not as a property access.
To disambiguate a variable name from subsequent characters in the string, enclose it in { and }; e.g., ${foo}.
This is especially important if the variable name is followed by a :, as PowerShell would otherwise consider everything between the $ and the : a scope specifier, typically causing the interpolation to fail; e.g., "$HOME: where the heart is." breaks, but "${HOME}: where the heart is." works as intended.
(Alternatively, `-escape the :: "$HOME`: where the heart is.", but that only works if the character following the variable name wouldn't then accidentally form an escape sequence with a preceding `, such as `b - see the conceptual about_Special_Characters help topic).
To treat a $ or a " as a literal, prefix it with escape char. ` (a backtick); e.g.:
"`$HOME's value: $HOME"
For anything else, including using array subscripts and accessing an object variable's properties, you must enclose the expression in $(...), the subexpression operator (e.g., "PS version: $($PSVersionTable.PSVersion)" or "1st el.: $($someArray[0])")
Using $(...) even allows you to embed the output from entire commands in double-quoted strings (e.g., "Today is $((Get-Date).ToString('d')).").
Interpolation results don't necessarily look the same as the default output format (what you'd see if you printed the variable / subexpression directly to the console, for instance, which involves the default formatter; see Get-Help about_format.ps1xml):
Collections, including arrays, are converted to strings by placing a single space between the string representations of the elements (by default; a different separator can be specified by setting preference variable $OFS, though that is rarely seen in practice) E.g., "array: $(#(1, 2, 3))" yields array: 1 2 3
Instances of any other type (including elements of collections that aren't themselves collections) are stringified by either calling the IFormattable.ToString() method with the invariant culture, if the instance's type supports the IFormattable interface[1], or by calling .psobject.ToString(), which in most cases simply invokes the underlying .NET type's .ToString() method[2], which may or may not give a meaningful representation: unless a (non-primitive) type has specifically overridden the .ToString() method, all you'll get is the full type name (e.g., "hashtable: $(#{ key = 'value' })" yields hashtable: System.Collections.Hashtable).
To get the same output as in the console, use a subexpression in which you pipe to Out-String and apply .Trim() to remove any leading and trailing empty lines, if desired; e.g.,
"hashtable:`n$((#{ key = 'value' } | Out-String).Trim())" yields:
hashtable:
Name Value
---- -----
key value
[1] This perhaps surprising behavior means that, for types that support culture-sensitive representations, $obj.ToString() yields a current-culture-appropriate representation, whereas "$obj" (string interpolation) always results in a culture-invariant representation - see this answer.
[2] Notable overrides:
• The previously discussed stringification of collections (space-separated list of elements rather than something like System.Object[]).
• The hashtable-like representation of [pscustomobject] instances (explained here) rather than the empty string.
#Joey has a good answer. There is another way with a more .NET look with a String.Format equivalent, I prefer it when accessing properties on objects:
Things about a car:
$properties = #{ 'color'='red'; 'type'='sedan'; 'package'='fully loaded'; }
Create an object:
$car = New-Object -typename psobject -Property $properties
Interpolate a string:
"The {0} car is a nice {1} that is {2}" -f $car.color, $car.type, $car.package
Outputs:
# The red car is a nice sedan that is fully loaded
If you want to use properties within quotes follow as below. You have to use $ outside of the bracket to print property.
$($variable.property)
Example:
$uninstall= Get-WmiObject -ClassName Win32_Product |
Where-Object {$_.Name -like "Google Chrome"
Output:
IdentifyingNumber : {57CF5E58-9311-303D-9241-8CB73E340963}
Name : Google Chrome
Vendor : Google LLC
Version : 95.0.4638.54
Caption : Google Chrome
If you want only name property then do as below:
"$($uninstall.name) Found and triggered uninstall"
Output:
Google Chrome Found and triggered uninstall