3

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.

Hermjard
  • 61
  • 4

2 Answers2

2

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'

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • "Therefore all visibility needs should be met even without the Private visibility property" - I have to admit, that I originally had a more complex intent, but wanted to start with a simplified question, to prevent too much counter questions. My original intent was, to make _functions_ of a module visibility-private, to have auxiliary functions in the module, which can access arbitrarily each other, but are not visible outside. Unfortunately, you achieve exactly the opposite with the **scope** private: It blocks inter-function-access, but still allows access by the importer. – Hermjard Dec 12 '20 at 20:27
  • Just for the case, that you wonder, how I made functions visibility-private (because unfortunately there is no _New-Function_ Cmdlet): After defining the function _Test-Function_, I put the following command at the end of the module: `(Get-Command -Name 'Test-Function').Visibility = [System.Management.Automation.SessionStateEntryVisibility]::Private`. – Hermjard Dec 12 '20 at 20:33
  • I want to avoid the Cmdlet `Export-ModuleMember` or `FunctionsToExport` respectively, because you have to adapt it everytime, you add an (exportable) new function to the module. The visibility _private_, according to its official description should be ideal for my purpose (and maybe even the reason for its existance), but I discovered, that it does not work as expected. And so I wanted to know, why. – Hermjard Dec 12 '20 at 20:40
  • Functions, that are not exported, are _not_ private. They are just not exported. This is a difference. And this makes a difference, if you try to solve the same problem when importing a ps1 file instead of a psm1 file. – Hermjard Dec 12 '20 at 21:23
  • "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." - Interestingly, a private _function_, which you attempt to access unsuccessfully, _is_ treated as if it didn't exist. – Hermjard Dec 12 '20 at 21:35
  • @mkelement0, your answer seems to suggest, that it is necessary to use _Get-Command_ to change the visibility of a function. But necessary alone is, to retrieve the function object, to change its visibility property. How you get this object, doesn't matter at all. Btw, the property _Visibility_ is not read-only _after_ importing the module. That means, you can even make a function _retroactively_ private from the shell. Btw, once you made it private, I dont know of a way to rollback this. – Hermjard Dec 12 '20 at 21:59
  • Ok, just found a way to rollback the invisibility: `(Get-Module MyModule).ExportedFunctions['Test-Function'].Visibility = 'Public'`. You can do this even when the function was made invisible already _within_ the module itself. You see, that a private function does _not_ disappear from the list of exported functions (_if_ exported). A hint for the notion, that Exports and Visibility are different concepts. Or would be different concepts, if the latter concept would work as intended. – Hermjard Dec 12 '20 at 22:30
  • It becomes complicated for me... are runspaces and containers the same? Or do you want to say, that visibility-privateness for variables and visibility-privateness for functions are different in their nature? And if you say, that visibility-privateness is not designed for intra-session use, why it is partly effective in intra-session use then? – Hermjard Dec 12 '20 at 22:47
  • @Hermjard: Clearly, what you call partial effectiveness amounts to a very haphazard, inconsistent, way-too-easily-bypassed manner. Again: Given the inconsistent, buggy behavior, the dearth of documentation hinting at cross-runspace use and - last but not least - that intra-runspace visibility / privacy is better managed with other, better-documented and non-buggy features, I see no value in pursuing this for _your_ purposes. Getting the docs amended is worth pursuing, but I don't expect it to be of relevance to most users. No, runspaces and containers aren't the same. – mklement0 Dec 13 '20 at 03:16
  • @Hermjard: A runspace is a single thread that runs PowerShell, such as an interactive session. In an interactive session, multiple runspaces come into play only implicitly, situationally, such as via background / thread jobs or remoting, or, in v7+. via `ForEach-Object -Parallel`. Via the PowerShell SDK, explicit runspace creation is possible. – mklement0 Dec 13 '20 at 15:07
1

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.

postanote
  • 15,138
  • 2
  • 14
  • 25
  • 1
    Yes, the help information is not clear to me. At least I am unable to exactly derive the described behavior from the help info. Maybe you could? Additionally, what do you mean with "dot sourcing/running such things loads stuff into the memory of your current session."? In the moment, you import a module, it _is_ already in the memory of your current session. Importing a module _is_ basically dot sourcing. And don't you notice a certain gap between the two statements "available only in the current scope" and " can’t view or change the variable on the console even if it is in the Global scope"? – Hermjard Dec 10 '20 at 21:05
  • Maybe a little bit more specific: "The variable is available only in the current scope." -> Ok, what _is_ the scope of the variable $mytest in my example then? And why is that scope not directly accessible by the console, but accessible by a scriptblock, which is invoked on the console? What is the special relation between the scope of that scriptblock and the scope of $mytest, which makes this possible? – Hermjard Dec 10 '20 at 21:14
  • The official documentation is indeed lacking, @Hermjard. postanote, you're quoting the `-Option` parameter, which is unrelated to he `-Visibility` parameter. The 2nd quote (a third-party source) doesn't really explain the feature, given that a module's variables are hidden from the importer _by default_. As demonstrated in the question, the visibility restriction is trivially bypassed. – mklement0 Dec 10 '20 at 21:54
  • Fixed the -option thing, so, yep, bade cut/paste from the doc. As for the 3rdP one, it' another source option. So, if what you are saying here is that both are invalid, (though I've not had reason to need what the OP is trying), then on the MS Docs side of this, one would assume one needs to raise a case with the MS PowerShell docs team to correct and/or provide more clarity for us all. – postanote Dec 11 '20 at 00:38