2

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
Jayson
  • 51
  • 2
  • 1
    `Write-Host $today$($saturday.allshift)` ... use the subexpression operator – Olaf Sep 23 '22 at 17:57
  • 1
    use parentheses so pwsh resolves what's within them first, `Write-Host $today($saturday.allshift)` – Santiago Squarzon Sep 23 '22 at 17:57
  • 3
    I actually answered another question regarded exactly that a few hours ago. As per other 2 comments, the subexpression operator is what you need there. This is because only simple variables get expanded in an expandable string. See my answer with references to the doc [here](https://stackoverflow.com/questions/73824211/powershell-microsoft-azure-commands-sql-database-model-azuresqldatabasemodel) – Sage Pourpre Sep 23 '22 at 18:08

1 Answers1

2

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775