What exactly means Visibility Private?

680 Views Asked by At

I write the following into a script module (i.e. a psm1 file) and do Import-Module for that file:

New-Variable -Name 'mytest' -Value 'Foo' -Scope 'Global'

Now I can access this variable in my PowerShell session. $mytest yields Foo, for example.

Alternatively, I write the following into a script module and do Import-Module for that file:

New-Variable -Name 'mytest' -Value 'Foo' -Scope 'Global' -Visibility Private

Now, the variable $mytest is not accessible in my PowerShell session anymore, fair enough.

But if I do . {$mytest}, I can get access on my "private" variable again - how comes? Just in case you would ask, & {$mytest} works the same, btw.

Note 1: Please be aware, that I don't ask about the (pseudo-)scope private, but about the visibility option private, which is a difference: The scope private blocks visibility "down the invocation hierarchy", whereas the visibility option private should block visibility "up the invocation hierarchy" (at least I have understood it that way).

Note 2: The global scope in the example above is only there, to be able to access the variable $mytest from the PowerShell session in the first attempt. Otherwise, even "free" public variables in a module ("free" meaning here, variables outside all scriptblocks) would not be accessible from the PowerShell session, except if you would use Export-ModuleMember -Variable 'mytest' (you would be forced to explicitly export everything else from the module, too, then). But this wouldn't change anything on the result.

2

There are 2 best solutions below

10
On

The good news is: I don't think the variable visibility feature is relevant in practice, at least not for PowerShell code that runs in a regular PowerShell session (typically in a console (terminal)).

As of this writing, the documentation is confusing, and seems inconsistent with the actual behavior.

about_Scopes: talks about how visibility relates to containers rather than scopes (emphasis added):

The Visibility property of a variable or alias determines whether you can see the item outside the container in which it was created. [...] A container could be a module, script, or snap-in. Items that have private visibility can be viewed and changed only in the container in which they were created. If the container is added or imported, the items that have private visibility cannot be viewed or changed. "

  • Snap-ins were binary-only module predecessors from v1, and are no longer relevant.

  • Scripts don't have state except while they're running, so the idea of accessing its variables from the outside generally doesn't apply (see below re dot-sourcing).

  • Modules do have state, but all variables in a module are hidden by default from the outside, without needing to set special variable properties. Conversely, you must explicitly request export of a variable via Export-ModuleMember -Variable (and also via the VariablesToExport entry in the associated module manifest, if present).

    • Note: For functions, the logic is different, because - unlike variables - all functions are exported by default. To make functions effectively private, i.e. to hide them from consumers of your module, you must switch to explicit exporting: If your module has no manifest, use an Export-ModuleMember -Function ... call at the bottom of your script module to select the functions to export explicitly. If you do have a manifest (a satellite *.psd1 file), it is sufficient to list the functions of interest in the FunctionsToExport entry. Note that both Export-ModuleMember and the FunctionsToExport module-manifest entry accept name patterns (wildcards); thus, for instance, if you adopt a convention of using the verb-noun naming convention only for public functions in your module, a FunctionsToExport = @( '*-*' ) entry would automatically limit exporting to the functions implicitly designated as public.

Therefore all visibility needs should be met even without the visibility property (set to Private):

  • As stated, modules hide their variables by default, but allow explicit exporting; similarly, what functions are visible from the outside can be controlled with explicit exporting (any functions you do not export are implicitly hidden, though without explicit exporting all are visible).

  • When a script or function runs, it can hide its variables from descendent scopes via the Private option / "scope"; e.g.:

      $private:foo = 'bar' # create variable only visible in the very same scope
      "in same scope: [$foo]"        # -> 'in same scope: [bar]'
      & { "in child scope: [$foo]" } # -> 'in child scope: []'
    
  • If a script is designed for dot-sourcing, i.e. to be loaded directly into the current scope with the . operator and you selectively want to hide variables created in it, the simplest solution is to enclose such variables in & { ... } in order to create them in a child scope that is discarded when the script block is exited; alternatively, but less robustly, you can also clean up explicitly via Remove-Variable


As for the visibility property getting enforced, as of PowerShell 7.1:

Dot-sourcing a script from the global scope seems to be the only scenario in which invisible variables then cannot directly be accessed in the caller's scope.

Running a command such as
New-Variable -Name 'mytest' -Value 'Foo' -Scope 'Global' -Visibility Private directly at the interactive prompt is tantamount to placing it in a script and dot-sourcing that script.

Attempting to access such a variable in the global scope directly then results in a statement-terminating error: PermissionDenied: Cannot access the variable '$mytest' because it is a private variable.

As you've observed, this restriction is trivially bypassed by accessing the variable via a script block, whether as . { $mytest } or as & { $mytest } - this works with hidden commands as well.

Dot-sourcing a script in a non-global scope, such as from inside another script, doesn't seem to enforce the restriction at all.

I don't know what the original design intent was, but:

  • I suspect that we're dealing with a bug that doesn't consistently enforce invisibility.

  • When it is enforced, it is curious that an invisible variable isn't simply treated as if it didn't exist, but an error is reported instead; by contrast, an invisible command indeed presents as if it didn't exist.[1]

  • The documentation of the feature as it relates to commands, [System.Management.Automation.CommandInfo]'s .Visible property, suggests that the feature is designed for out-of-runspace access (emphasis added).

    • "Indicates if the command is to be allowed to be executed by a request external to the runspace."

    • The implication is that visibility isn't designed for intra-session use, given that a regular PowerShell session is confined to a single runspace - except if you explicitly create additional runspaces via the PowerShell SDK.


[1] As you've discovered, it is possible to control the visibility of commands - which includes functions - too, namely via a command's [System.Management.Automation.CommandInfo] representation, such as returned by the Get-Command cmdlet; e.g.: (Get-Command myFunc).Visibility = 'Private'

4
On

Are you saying the help information in the bulti-in help file New-Variable is not clear:

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/new-variable?view=powershell-7.1

-Visibility

Determines whether the variable is visible outside of the session in which it was created. This parameter is designed for use in scripts and commands that will be delivered to other users. The acceptable values for this parameter are:

Public. The variable is visible. (Public is the default.) Private. The variable is not visible.

When a variable is private, it does not appear in lists of variables, such as those returned by Get-Variable, or in displays of the Variable: drive. Commands to read or change the value of a private variable return an error. However, the user can run commands that use a private variable if the commands were written in the session in which the variable was defined.

Or this article:

PowerShell variable properties: Description, Visibility, Options, Attributes

Variable visibility

The Visibility property of a PowerShell variable can have the values Public or Private. Public is the default. Don’t confuse this property with the Private scope. The Visibility property allows a developer of a PowerShell module to hide variables. If you import a module with variables where Visibility is set to Private, you can’t view or change the variable on the console even if it is in the Global scope. If you try to access a Private variable, PowerShell will throw an error message: Cannot access the variable '$myPrivateVariable' because it is a private variable.

Of course, dot sourcing/running such things loads stuff into the memory of your current session.