How can you use an object's property in a double-quoted string?
Asked Answered
I

5

155

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?

Ironbound answered 17/7, 2009 at 21:11 Comment(0)
S
248

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.

Signore answered 17/7, 2009 at 21:41 Comment(2)
This works but I wonder if i may as well just concatenate strings as I was trying to avoid in the first placeCrusted
@ozzy432836 You can, of course. Or use format strings. It usually doesn't really matter and comes down to personal preference.Signore
B
24

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.

Basal answered 6/11, 2016 at 4:29 Comment(2)
Is there a proper name for ${..}?Incommunicable
@AbrahamZinala, not that I'm aware of. Perhaps variable-reference-with-disambiguated-name-via-curly-braces-enclosure? (Just kidding.)Basal
Z
20

@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.

Zinn answered 17/7, 2009 at 21:11 Comment(4)
Nice find. I actually wasn't completely sure what his problem was but trying to access properties from within a string smelled bad :)Signore
Thanks guys! Johannes why do you think accessing properties in a string smells bad? How would suggest it is done?Ironbound
caveman: It just was a thing that caught my eye as being a potential error cause. You can certainly do it and it's nothing weird, but when omitting the $() operator it screams "Fail" because it can't work. Thus my answer was just an educated guess about what your poblem could be, using the first possible failure cause I could see. Sorry if it looked like it, but that comment wasn't referring to any best practice or so.Signore
Good you had me worried there! ;) When I first started using powershell I thought the variable in a string was a bit weird, but it is quite handy. I've just not managed to find a definitive place that I can look for syntax help.Ironbound
B
11

@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
Brno answered 29/7, 2016 at 18:28 Comment(4)
Yeah that is more readable especially if you are used to .net code. Thanks! :)Ironbound
There's a gotcha to look out for when you first use format strings, which is that you will often need to enclose the expression in parens. You cannot, for example simply write write-host "foo = {0}" -f $foo since PowerShell will treat -f as the ForegroundColor parameter for write-host. In this example you would need to write-host ( "foo = {0}" -f $foo ). This is standard PowerShell behaviour, but worth noting.Nose
Just to be pedantic, the example above is not "string interpolation", it is "composite formatting". Since Powershell is inextricably bound to .NET for which C# and VB.NET are the primary programming languages, the terms "string interpolation" and "interpolated string" (and anything similar) should be applied only to the new $ prepended strings found in those languages (C# 6 and VB.NET 14). In these strings, parameter expressions are directly embedded in the string instead of numeric parameter references, with the actual expressions then being arguments to String.Format.Colunga
@andyb, regarding format string "gotchas". This is actually the difference between Expression and Argument mode (see about_Parsing). Commands are parsed into tokens (groups of characters forming a meaningful string). Space separates tokens that would merge but is otherwise ignored. If the command's 1st token is a number, variable, statement keyword (if, while, etc), unary operator, {, etc... then the parsing mode is Expression otherwise Argument (up to a command terminator).Colunga
B
7

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
Boudreaux answered 22/10, 2021 at 22:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.