Written as of PowerShell Core 6.2.0
The reason is that PowerShell treats the following:
${<drive>:<name>}
as if you had specified:
Get-Content -Path <drive>:<name> # or, with assignment, Set-Content -Path ...
This notation - though often used with the Env:
drive (e.g., $env:Path
) - is little-known as a general paradigm named namespace variable notation, which is explained in this answer.
The problem is the use of -Path
rather than -LiteralPath
, because -Path
interprets its argument as a wildcard expression.
Therefore, the [foo]
in ${env:[foo]}
- rather than being used as-is - is interpreted as a wildcard expression that matches a single character that is either f
or o
([foo]
is a character set or range ([...]
) that matches any one of the (distinct) characters inside - see about_Wildcards).
On assigning to ${env:[foo]}
, the logic of Set-Content -Path
requires that a wildcard-based path resolve to something existing, even though you're generally not required to explicitly create environment variables; e.g., ${env:NoSuchVarExistsYet} = 'new'
works just fine.
Workaround:
Use double(!)-`
-escaping of the wildcard metacharacters:
# Namespace variable notation only works with if you
# double(!)-backtick-escape the wildcard metacharacters:
# Assign to / implicitly create env. var '[foo]'
${env:``[foo``]} = 'bar'
# Get its value.
${env:``[foo``]}
Note:
Escaping shouldn't be required at all, because there is no good reason to treat paths that conceptually identify a given, known item as wildcard expressions - see GitHub issue #9225.
That double `
-escaping is needed is an added quirk - see GitHub issue #7999.
Another workaround - one that doesn't involve escaping - is to use
Set-Content -LiteralPath env:[foo] bar
and Get-Content -LiteralPath env:[foo]
, but that is both verbose and slow.
As for the other syntax variations you tried:
$env:${[foo]}="bar"
Since your variable reference isn't {...}
-enclosed as a whole (except for the initial $
), the token that follows the :
is only allowed to contain characters that do not require escaping - and $
, {
and }
all violate that rule.
{...}
-enclosing the entire path - ${env:[foo]}
- solves the syntax problem, but runs into the problem detailed above.
Set-Item -LiteralPath env:${[foo]} -Value "bar"
This does not work in general, because string expansion is applied beforehand here - it is as if you had passed "env:${[foo]}"
: the reference to a (regular) variable named ${[foo]}
is expanded (replaced with its value) and in effect appended to literal env:
, before handing the result to Set-Item
.
If such a regular variable doesn't exist, what Set-Item
sees is just env:
(because non-existent variables default to $null
, which becomes the empty string in a string context), which causes an error due to the lack of variable name.
By contrast, the following would set an environment variable named unrelated
instead:
# Create a regular variable literally named '[foo]'.
${[foo]} = 'unrelated'
# !! The following sets env:unrelated, i.e., env. var 'unrelated',
# !! due to the string expansion that is performed on the -LiteralPath
# !! argument up front.
Set-Item -LiteralPath env:${[foo]} bar
$env:unrelated # -> 'bar'
The same applies to Get-Item -LiteralPath env:${[foo]}
and
Set-Item -LiteralPath env:${[foo]2} -Value "bar"
.