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

0 votes
asked Jul 17, 2009 by caveman-dick

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?

3 Answers

0 votes
answered Jul 17, 2009 by joey

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.

0 votes
answered Jul 6, 2016 by mklement0

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, including in double-quoted here-strings):

  • Only references such as $foo, $global:foo (or $script:foo, ...) and $env:PATH (environment variables) are recognized when directly embedded in a "..." string - that is, only the variable reference itself is expanded, irrespective of what follows.

    • 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.").

    • 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 command lines 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 $OFS) 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 IFormttable 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 and 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 of mine.

[2] Notable exception:
String-expanding a [pscustomobject] results in a hashtable-like representation via .psobject.ToString() (explained here), whereas calling .ToString() directly yields the empty string.

0 votes
answered Jul 29, 2016 by loonison101

@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
Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...