Does PowerShell supports private static variables in a PowerShell module (like a private static variable in C#)? If so, do I need a "special" syntax for it?
You should take a look at the about_Scopes documentation.
There is a script scope:
Script:
The scope that is created while a script file runs. Only
the commands in the script run in the script scope. To
the commands in a script, the script scope is the local
scope.
Also worth reading - Restricting Without Scope:
Modules:
You can use a Windows PowerShell module to share and deliver
Windows PowerShell tools. A module is a unit that can contain
cmdlets, scripts, functions, variables, aliases, and other useful
items. Unless explicitly defined, the items in a module are not
accessible outside the module. Therefore, you can add the module to
your session and use the public items without worrying that the
other items might override the cmdlets, scripts, functions, and other
items in your session.
The privacy of a module behaves like a scope, but adding a module
to a session does not change the scope. And, the module does not have
its own scope, although the scripts in the module, like all Windows
PowerShell scripts, do have their own scope.
Related
I have an Azure Automation PowerShell runbook that calls particular child runbooks based on input parameters. The child runbooks perform actions with different PowerShell modules. I believe Azure Automation is automatically loading/importing the required modules at the moment the child runbook is called. I would like to prevent this behaviour, and import modules manually using import-module.
In an Azure Automation account, is it possible to control if/when required modules are imported?
Edit:
Microsoft documentation outlining conditions where modules are imported.
This behavior, in Powershell, is controlled by one of the preference variables. More specifically, the $PSModuleAutoloadingPreference variable.
This variable control the automatic importing of module in the session.
By default, it is set to All, which automatically import modules on first-use.
Set this to None to disable the automatic importing of modules. You will need to explicitely use Import-Module for any command to be loaded.
Here's a snippet of the official documentation regarding preferences variiables and that variable specifically.
About Preference Variables
PowerShell includes a set of variables that enable you to customize
its behavior. These preference variables work like the options in
GUI-based systems.
The preference variables affect the PowerShell operating environment
and all commands run in the environment. In many cases, the cmdlets
have parameters that you can use to override the preference behavior
for a specific command.
...
$PSModuleAutoloadingPreference
Enables and disables automatic importing of modules in the session.
All is the default. To import a module, get or use any command in the
module. For example, use Get-Command. The
$PSModuleAutoloadingPreference variable does not exist by default. The
default behavior when the variable is not defined is the same as
$PSModuleAutoloadingPreference = 'All'.
Regardless of the variable's value, you can use Import-Module to
import a module.
The $PSModuleAutoloadingPreference variable takes one of the
PSModuleAutoLoadingPreference enumeration values: None,
ModuleQualified, or All.
Valid values are:
All: Modules are imported automatically on first-use.
ModuleQualified: Modules are imported automatically only when a user uses the module-qualified name of a command in the module. For
example, if the user types MyModule\MyCommand, PowerShell imports the
MyModule module.
None: Automatic importing of modules is disabled in the session. To import a module, use the Import-Module cmdlet.
Source: about_Preference_Variables
I'm trying to create a powershell module to store some reusable utility functions. I created script module PsUtils.psm1 and script module manifest PsUtils.psd1 (used these docs). My problem is that when I import this module in another script Visual Code does not suggest parameters names. Here's a screenshot:
When I hover cursor over the function I only get this:
PsUtils.psm1
function Get-Filelist {
Param(
[Parameter(Mandatory=$true)]
[string[]]
$DirectoryPath
)
Write-Host "DIR PATH: $DirectoryPath"
}
PsUtils.psd1 (excerpt)
...
FunctionsToExport = '*'
I have Powershell extension installed. Do I need to install anything else to make the suggestions work? What am I missing?
Generally speaking, only auto-loading modules - i.e., those in one of the directories listed in environment variable $env:PSModulePath - are automatically discovered.
As of version v2022.7.2 of the PowerShell extension, the underlying PowerShell editor services make no attempt to infer from the current source-code file what modules in nonstandard directories are being imported via source code in that file, whether via Import-Module or using module
Doing so would be the prerequisite for discovering the commands exported by the modules being imported.
Doing so robustly sounds virtually impossible to do with the static analysis that the editor services are limited to performing, although it could work in simple cases; if I were to guess, such a feature request wouldn't be entertained, but you can always ask.
Workarounds:
Once you have imported a given module from a nonstandard location into the current session - either manually via the PIC (PowerShell Integrated Console) or by running your script (assuming the Import-Module call succeeds), the editor will provide IntelliSense for its exported commands from that point on, so your options are (use one of them):
Run your script in the debugger at least once before you start editing. You can place a breakpoint right after the Import-Module call and abort the run afterwards - the only prerequisite is that the file must be syntactically valid.
Run your Import-Module command manually in the PIC, replacing $PSScriptRoot with your script file's directory path.
Note: It is tempting to place the cursor on the Import-Module line in the script in order to use F8 to run just this statement, but, as of v2022.7.2, this won't work in your case, because $PSScriptRoot is only valid in the context of running an entire script.
GitHub issue #633 suggests adding special support for $PSScriptRoot; while the proposal has been green-lighted, no one has stepped up to implement it since.
(Temporarily) modify the $env:PSModulePath variable to include the path of your script file's directory.
The most convenient way to do that is via the $PROFILE file that is specific to the PowerShell extension, which you can open for editing with psedit $PROFILE from the PIC.
Note: Make sure that profile loading is enabled in the PowerShell extension's settings.
E.g., if your directory path is /path/to/my/module, add the following:
$env:PSModulePath+="$([IO.Path]::PathSeparator)/path/to/my/module"
The caveat is that all scripts / code that is run in the PIC will see this updated $env:PSModulePath value, so at least hypothetically other code could end up mistakenly importing your module instead of one expected to be in the standard locations.
Note that GitHub issue #880 is an (old) proposal to allow specifying $env:PSModulePath entries as part of the PowerShell extension settings instead.
On a somewhat related note:
Even when a module is auto-discovered / has been imported, IntelliSense only covers its exported commands, whereas while you're developing that module you'd also like to see its private commands. Overcoming this limitation is the subject of GitHub issue #104.
I'm writing a PowerShell module that is using as wrapper for .Net (DotNet) Assembly 'Framework.Core.Measurements.dll'.
The PowerShell version is 5.1.* under Windows 7 64 bits.
In my .psd1 file added the assembly to the RequiredAssemblies list the assembly:
RequiredAssemblies = #('Framework.Core.Measurements.dll')
I
In my .psm1 I added "using namespace" statement:
using namespace Framework.Core.Measurements
I used "Import-Module -Verbose" to verify that .pdm1 is indeed being executed.
After imported successfully I try to use the Angle enum I have in the "Framework.Core.Measurements" namespace, but PowerShell does not recognized it despite the "using namespace ..." statement that was executed in the .psm1 script.
If I type the "using namespace Framework.Core.Measurements" statement in the PowerShell command prompt then it works.
Any idea or work around?
You cannot import namespaces for the importer of your module via a *.psm1 file:
A using namespace statement takes effect for the scope in which it is executed and all descendant scopes, which is why executing such a statement inside a *.psm1 file has no effect on the scope that imports the module.
The workaround is to place the call in a *.ps1 script that you must reference from your module manifest's ScriptsToProcess entry (see the New-ModuleManifest help topic).
Such scripts are run directly in the importing code's scope (they are dot-sourced), but note that this only happens the first time a given module is imported into a session - see this GitHub issue.
Note: While assemblies referenced via the RequiredAssemblies entry are loaded before the scripts specified via ScriptsToProcess are dot-sourced in the caller's scope (see below), the order doesn't really matter:
As you've discovered, PowerShell allows you to execute a using namespace statement irrespective of whether any assemblies with types in that namespace are loaded at that time; instead, PowerShell looks in whatever namespaces you've previously passed to using namespace whenever you attempt to access a type by mere name (e.g., [Angle]).
As an aside: Generally, the elements of a module being imported are executed in the following order:
Loading of the assemblies specified in RequiredAssemblies.
Dot-sourcing of the scripts specified in ScriptsToProcess.
Recursive importing of any nested modules specified in NestedModule.
Importing of the main module specified in RootModule.
I didn't consider using ScriptsToProcess the first place cause accoring to the documentation it's scripts ran prior to the module importing.
That is, I was certain that the .Net assembly should be already loaded before running "using namespace" for any of namespaces it has.
Now I realize that one can run using namespace statement even though there is no code that is currently associated with it.
Your answer helped me alot.
Thank you.
I keep looking at this, and I cannot for the life of me understand why PSModulePath is an environment variable. Is there any rational reason for this?
To explain further, $profile is a PowerShell variable. You can invoke it simply by typing $profile. This makes sense. However, PSModulePath has absolutely no relevance or function outside of PowerShell, and yet, for some unintelligble decision, it has been set as an environment variable. i.e. it cannot be viewed by $PSModulePath, and can only be viewed by doing echo $env:PSModulePath. Making it even more irrational is that there is a $PSModuleAutoLoadingPreference which again is a PowerShell variable and not an Environment variable ...
The PSModulePath variable is 100% only for PowerShell. Can anyone explain a rationale for this extremely odd setup?
Because $env:PSModulePath is required before the Powershell session is loaded. I believe (and I could be wrong on the "why" here) this is because several "built-in" cmdlets are defined within modules themselves, and need to be loaded on startup. Notably, you'll see this if you look at a specific module directory defined in $env:PSModulePath, which is the System module path:
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
Generally you don't want to install your modules here and should remain "clean", as in, let MS manage the modules in this directory.
I have a powershell module I am writing and one thing I was curious of that is a bit unclear is the ScriptsToProcess Key. Can or should I use this to verify certain things like OS Type, Bit Type, or the presence or lack of certain environment variables to throw warnings if some of my functions may rely on some of the requirements above?
You can use ScriptsToProcess to execute script in the caller's session state (create variables, etc) or to use Write-Warning to notify the user the prereqs haven't been met. However, even if you throw an error, the module is still loaded. So if your intent is to prevent loading of the module I would put the warnings/throws in the startup script of your ModuleToProcess/RootModule. The module will show up in the Get-Module list but there shouldn't be any exported commands. Also if you want to check bitness, use the module manifest ProcessorArchitecture key.