PowerShell date time not combining strings System.Collections.Hashtable.allshift

Viewed 24

I am attempting to make a string the contains the bellow hash table object strings and the $today variable and resulting with System.Collections.Hashtable.allshift.

$today = (Get-Date).ToString('yyyyMMdd')

$saturday = @{
    allshift = "All_Shifts_Checklist_Saturday.xlsx";
    elements = "Elements_Weekend.xlsx";
    night = "Night_Shift_Saturday.xlsx"
}

Write-Host $today$saturday.allshift

###ACTUAL 20220923System.Collections.Hashtable.allshift
###EXPECTED 20220923All_Shifts_Checklist_Saturday.xlsx
1 Answers

Building on the helpful comments:

  • Because argument $today$saturday.allshift is implicitly treated as if it were enclosed in "...", the interpolation rules of expandable strings apply.

  • Inside "..." strings, only stand-alone variable references are expanded as-is by default, e.g., $saturday, and not also expressions, which includes property access, e.g. $saturday.allshift. In the latter case, $saturday is expanded as a whole, and .allshift is printed verbatim.

    • In order to embed expressions such as $saturday.allshift inside "...", you need $(...), the subexpression operator:

      # "..." isn't strictly necessary, but is conceptually clearer.
      Write-Host "$today$($saturday.allshift)"
      
    • See this answer for more information, and this answer for a comprehensive overview of the string-interpolation rules.


What makes your example tricky are the subtleties around how PowerShell decides whether a given command argument is implicitly treated as if it were "..."-enclosed:

A few examples (Write-Output is used instead of Write-Host, to make it more obvious when a compound token is actually passed as two arguments):

# Argument is treated as an *expression*
# -> 'All_Shifts_Checklist_Saturday.xlsx'
Write-Output $saturday.allshift

# The compound token treats $saturday.allshift as an *expression*,
# and $today therefore becomes a *separate argument*.
# -> 'All_Shifts_Checklist_Saturday.xlsx', '20220923'
Write-Output $saturday.allshift$today

# Your case:
# Because the compound token starts with a stand-alone variable reference
# *followed by additional characters* that do not constitute a property
# access or method call, the whole token is considered a single, expandable
# string:
#  * $today expands as intended
#  * $saturday expands *as a whole* - resulting in just the *type name* of 
#    the hashtable, 'System.Collections.Hashtable' (try `@{}.ToString()`)
#  * and '.allshift' is treated as a literal.
# -> '20220923System.Collections.Hashtable.allshift'
Write-Output $today$saturday.allshift
  • That is, if a compound token starts with a variable-based expression that is a property access or method call, the expression is recognized as such, and whatever follows becomes a separate argument.

    • The exact rules are complex; this answer discusses them.
  • Therefore, it is best to form a habit to enclose compound tokens that are meant to be a single string argument in "...", and use $(...) to enclose any embedded expressions or commands.

Related