Powershell: Many PowerShell Automatic Variables are not available to custom class methods

Created on 7 Jun 2020  Â·  8Comments  Â·  Source: PowerShell/PowerShell

Steps to reproduce

class FooClass { FooClass() { Write-Host $HOME } }

Expected behavior

I would have expected that class objects would be able to read all PowerShell Automatic Variables as if they were globals.

Actual behavior

ParserError:
Line |
   1 |  class FooClass { FooClass() { Write-Host $HOME } }
     |                                           ~~~~~
     | Variable is not assigned in the method.

Interestingly, this works:

function Get-Home { return $HOME }
class FooClass { FooClass() { Write-Host $(Get-Home) } }

I assume this is a similar/related issue to #9174, but for tracking purposes (in e.g. #6652) it seems like it would be useful to have an issue for the general case. I went through the list and it seems that the following Automatic Variables are not available within class methods:

$$
$^
$ConsoleFileName
$Event
$EventArgs
$EventSubscriber
$ExecutionContext
$HOST
$IsCoreCLR
$IsLinux
$IsMacOS
$IsWindows
$PID
$PROFILE
$PSCulture
$PSDebugContext
$PSHOME
$PSUICulture
$PSVersionTable
$Sender
$ShellId

Whereas these Automatic Variables are available within class methods:

$?
$_
$args
$Error
$false
$foreach
$input
$LastExitCode
$Matches
$MyInvocation
$NestedPromptLevel
$null
$PSBoundParameters
$PSCmdlet
$PSCommandPath
$PSItem
$PSScriptRoot
$PWD
$StackTrace
$switch
$this
$true

Environment data

Name                           Value
----                           -----
PSVersion                      7.0.1
PSEdition                      Core
GitCommitId                    7.0.1
OS                             Microsoft Windows 10.0.18363
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Question WG-Engine

Most helpful comment

For the majority (more on that at the end) that are available, it's because they are either compiler constants (e.g. $true, $false, $null) or they could be valid locals based on context.

For the latter, take this example:

class Test {
    static [void] Do() {
        0..10 | & { $input }
    }
}

While $input, $PSCmdlet, $matches, etc are not always valid locals, determining statically if they are valid is next to impossible (or at the very least way too expensive).

The automatic vars that don't work are always globals. You can always access these explicitly by specifying scope, e.g. $global:Host. Personally I prefer this, if you want to access state outside of your class you should (imo) be required to explicitly declare that you're doing so.

All that said, I think the list should be further restricted. These variables are never (afaik) actually locals (or even if they are technically, I think they act more like globals):

$?
$Error
$LastExitCode
$NestedPromptLevel
$PWD
$StackTrace

All 8 comments

Taking a quick look, seems like the former variables are not flagged with AllScope attribute whereas the latter are.

I imagine there are some design decisions behind that, but can't personally speak to them. @daxian-dbw @rjmholt might be the best folks to ask (or will know who to ask if not, I suppose). 🙂

For the majority (more on that at the end) that are available, it's because they are either compiler constants (e.g. $true, $false, $null) or they could be valid locals based on context.

For the latter, take this example:

class Test {
    static [void] Do() {
        0..10 | & { $input }
    }
}

While $input, $PSCmdlet, $matches, etc are not always valid locals, determining statically if they are valid is next to impossible (or at the very least way too expensive).

The automatic vars that don't work are always globals. You can always access these explicitly by specifying scope, e.g. $global:Host. Personally I prefer this, if you want to access state outside of your class you should (imo) be required to explicitly declare that you're doing so.

All that said, I think the list should be further restricted. These variables are never (afaik) actually locals (or even if they are technically, I think they act more like globals):

$?
$Error
$LastExitCode
$NestedPromptLevel
$PWD
$StackTrace

Also maybe the $Event* variables should work without an explicit scope modifier. Though I suppose they're used infrequently enough that requiring $local:Event is probably fine. Actually it seems like using the local or private modifiers still throw, that should maybe change.

if you want to access state outside of your class you should (imo) be required to explicitly declare that you're doing so.

FWIW I agree with this. I was unaware that (some of the) PowerShell Automatic Variables could be referenced via their $global: scope, even though I saw them there in the list returned by Get-Variable -Scope global, I guess I thought there was just something _special_ about Automatic Variables haha.

I initially ran across this issue when I was cleaning up a class for cross-platform compatibility — I wanted to replace a bunch of $env:USERPROFILE references with the cross-platform-friendly $HOME instead. For some other properties, such as user name or host/computer name, I have read that the [Environment] static class properties [Environment]::UserName and [Environment]::MachineName have been implemented in a cross-platform-friendly fashion. This is getting beyond the scope of this specific issue but I wonder if the [Environment] class could be fleshed out further to include cross-platform implementations for all common environment variables shared by platforms PowerShell supports?

You can use [Environment]::GetEnvironmentVariable($name) to retrieve specific environment variables in a similar way. The ones available directly on the class seem to be pretty sparse in comparison to the ones you could use.

The class itself is in the .NET core libraries, though. If you want to suggest adding a few extra static members there, it would be best to ask in the dotnet/runtime repo. :slightly_smiling_face:

This is getting beyond the scope of this specific issue but I wonder if the [Environment] class could be fleshed out further to include cross-platform implementations for all common environment variables shared by platforms PowerShell supports?

Most of the directory based ones should be available through GetFolderPath, e.g.

[Environment]::GetFolderPath(
    [Environment+SpecialFolder]::LocalApplicationData)

Will return the equivalent of $env:LOCALAPPDATA on Windows and on *nix it'll be either $env:XDG_DATA_HOME or $HOME/.local/share.

@IarwainBen-adar One of the decisions we made when implementing classes was to provide a more robust programming environment for PowerShell where static checks verify that variables are set before they are used. Obviously this has to be scoped to the method being defined so if the variable you're referencing is not automatic or local, you must qualify the reference so the compiler knows to skip that variable. Personally I've found this to be super useful when writing large PowerShell scripts. I just throw everything into classes and methods instead of functions and get a ton of help from the static analyzer.

@bpayette I agree completely! For the most part I've been very happy with PowerShell classes. I was just surprised to learn that not all Automatic Variables are alike. For the Automatic Variables that truly are global, referencing them by their $global: scope seems perfectly appropriate.

Was this page helpful?
0 / 5 - 0 ratings