Install-Module not available - not recognized as a name of a cmdlet - powershell

I'm trying to take advantage of the PowerShell Gallery which requires Install-Module. For some reason I cannot get this to work on any computer I have access to:
Install-Module: The term 'Install-Module' is not recognized as a name of a cmdlet, function, script file, or executable program.
I tried this on a Windows 8.1, Windows 10 and even installed PowerShell 7.1 on the Windows 10 computer. I see that a lot of people have this issue, but other than installing WMF (which seems to already be included with Win10) I don't see any useful suggestions.
I don't have a lot of experience with PowerShell, and definitely not managing it, so I'm curious if anybody has an idea as to why this is not working.
Edit 1:
I seem to have the PowerShellGet module in the following directories:
C:\Program Files\WindowsPowerShell\Modules
C:\Program Files\PowerShell\7\Modules
and the $env:PSModulePath variables contains C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
Edit 2:
This may be related to the __PSLockDownPolicy environment variable, that was set to 4 on all of the affected computers. I was able to figure this out by attempting to manually load the PowerShellGet module, which yielded an error that hinted at this.
After removing that environment variable, it seems to work.

As you've discovered yourself, it was the presence of machine-level environment variable __PSLockDownPolicy that prevented Install-Module from running (see below for general prerequisites).
This undocumented environment variable, effective on Windows only, is for security reasons only respected if defined at the machine level via the registry (rather than as a per-user or process-specific variable) and can be used to restrict what is allowed in a PowerShell session.
Values 4 through 7 result in PowerShell operating in constrained language mode, where only a handful of pre-approved .NET types may be used, which as a result prevents the PowerShellGet module that contains the Install-Module from loading - see the conceptual about_Language Modes help topic.
You can check what language mode is in effect in a given session by executing $ExecutionContext.SessionState.LanguageMode; in constrained mode, the result is ConstrainedLanguage; FullLanguage indicates that no language restrictions are in place.
Looking at PowerShell's source code, the fact that this variable is checked inside a method named GetDebugLockdownPolicy() suggests that it isn't officially supported.
For more information, see this archived blog post.
The simplest way to remove this restriction is to delete the environment variable, by running the following from an elevated PowerShell prompt:
Remove-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' __PSLockdownPolicy
One mystery remains:
As you state, importing the PowerShellGet module explicitly, with Import-Module PowerShellGet showed you that the real problem was not that the Install-Module command didn't exist, but that its enclosing module failed to load.
Doing so revealed that yet another module was the problem, namely the PackageManagement module that PowerShellGet depends on; Import-Module PackageManagement finally reveals: Cannot set property. Property setting is supported only on core types in this language mode, which implies that the (constrained) language mode was the problem.
The mystery is the error message reported in your question: Install-Module: The term 'Install-Module' is not recognized as a name of a cmdlet, function, script file, or executable program.
I see a different error message, both in Windows PowerShell 5.1 and PowerShell (Core) 7.1.2, when the constrained language mode is in effect, i.e. when $ExecutionContext.SessionState.LanguageMode reports ConstrainedLanguage:
Install-Module: The 'Install-Module' command was found in the module 'PowerShellGet', but the module could not be loaded. For more information, run 'Import-Module PowerShellGet'.
This error message makes it more obvious what the problem is, and would probably have led to a faster diagnosis.
The following contains general information that is unrelated to the above:
Installing / troubleshooting of the PowerShellGet module that provides the Install-Module command:
Install-Module is a (function-based) cmdlet that comes with Windows PowerShell 5.0 and 5.1 as well as all versions of PowerShell (Core) (v6+), as part of the PowerShellGet module.
If your computers do meet these criteria, the implication is that your PowerShell installation has been corrupted in one or both of the following ways:
The PowerShell get module was removed (or renamed) from its default location
(C:\Program Files\WindowsPowerShell\Modules\PowerShellGet in Windows PowerShell,
C:\Program Files\Powershell\7\Modules\PowerShellGet in PowerShell (Core) 7.x, on Windows).
Either reinstall PowerShell or try the manual installation instructions linked to below.
The $env:PSModulePath environment variable is missing an entry for the parent directory of the respective default location (that is,
C:\Program Files\WindowsPowerShell\Modules /
C:\Program Files\PowerShell\7\Modules). Without this entry, PowerShell cannot find the PowerShellGet module (by name only).
Manually update the machine-level definition of environment variable PSModulePath via sysdm.cpl, tab Advanced, button Environment Variables... to include the applicable directory.
If your computers do not meet these criteria, you have two choices:
If you can, upgrade the Windows PowerShell version to v5.1 or switch to PowerShell (Core).
Alternatively - if your Windows PowerShell version is 3 or 4 - install PowerShellGet manually, as per the official instructions.
Note: You may have to have access to at least one computer that already has a functioning copy of PowerShellGet to source the manual installation from.

Related

How does Powershell Auto-Load Modules?

We learn that Powershell introduced Module Auto-Loading in 3.0 :
... PowerShell imports modules automatically the first time that you
run any command in an installed module. You can now use the
commands in a module without any set-up or profile configuration, ...
And this is done via PSModulePath.
What the docs fail to explain is how Powershell can detect which commands are in a module without first loading the module.
That is, when I use Import-Module, I "know" that Powershell will execute (??!) the powershell code in my .psm1 file, exporting all functions, ... or whatever I spec with Export-Modulemember.
However, the Auto-Load feature has to know before hand that a certain command is available via a certain module without actually loading the module.
Since we had some misbehaving third party modules in PsModulePath and since we have a very few modules that we wrote ourselves that we like to anchor in PSModulePath, I would very much like to understand how the files in PSModulePath are processed.
This is a partial answer.
This is done/implemented via Get-Command and this seems to be enabled to "parse" module files without actually executing the PoSh Code there. See below.
From the powershell docs:
Implicitly Importing a Module
... works on any module in a directory that is included in the value
of the PSModulePath environment variable ...
To support automatic importing of modules, the Get-Command cmdlet
gets all cmdlets and functions in all installed modules, even if the
module is not imported into the session. ...
And then:
Get-Command
The Get-Command cmdlet gets all commands that are installed on the
computer ...
Get-Command that uses the exact name of the command, without
wildcard characters, automatically imports the module that contains
the command so that you can use the command immediately. ...
Get-Command gets its data directly from the command code, unlike
...
The docs do not explain how this is implemented, but one could possibly look up how it's done in the source code. (I wasn't so far able to find it, despite browsing the sources for a while.)
Incidentally I find mentioned that Powershell 3 was the first version to expose the AST, so it stands to reason that the posh code does exactly that: Parse the Scripts and inspect their AST in some way to determine if the command is provided.

PowerShell Core and AppX package management

I am using PowerShell 6.2 preview at the moment. In my script I am trying to do stuff with Windows 10 apps. To be able to use commands like Get-AppxPackage, I need to import Windows modules from previous PowerShell like so:
Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Appx\Appx.psd1 -SkipEditionCheck
Import-Module C:\Windows\system32\WindowsPowerShell\v1.0\Modules\dism\dism.psd1 -SkipEditionCheck
Does PowerShell core has its own modules to work with this? I found Get-Package for example, but that does not give me anything.
Since this is one of the top search results for PowerShell Core Get-AppxPackage, I'm going to take the information from the link provided in the comments and provide an answer, with example.
As LangsGalgEnRad pointed out in the comments, it's easiest just to do this from Windows PowerShell, but ultimately that's just-shy-of-deprecated at this point, with Microsoft stating that there are to be no more fixes or changes other than critical security issues. That said, it's still (afaik) universally available in Windows installations.
But for those of us who want to follow Microsoft's advice to use PowerShell Core, LangsGalgEnRad also points out in the comments the WindowsCompatibility module from Microsoft. Reading the blog post, this seems a bit safer than importing a Windows module (e.g. AppX) from PowerShell Core, since among other things ...
WindowsCompatibility is very careful to not overwrite native PowerShell core commands.
To install from PowerShell Gallery:
Install-Module WindowsCompatibility
Example usage for AppX:
Import-Module WindowsCompatibility
Import-WinModule AppX
Get-AppxPackage

Same module different casing. Why?

There is a bit of a problem going on with minikube and docker-machine on windows, determining if hyper-v module is available. For more information:
https://github.com/kubernetes/minikube/issues/2634
https://github.com/docker/machine/issues/4424
but this is not essential.
What is, that on two Windows 10 machines, both up-to-date, both with WMF 5.1 (PSVersion 5.1.16299.251) When running Get-Command hyper-v\get-vm we get different results.
Let me demonstrate:
The difference is that the module name differs. It's "hyper-v" vs "Hyper-V".
I compared byte-for-byte files inside C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Hyper-V. They are the same, and the path name have the same casing.
So why is the difference?
This is likely caused by how the module is imported:
Import-Module Hyper-v
(Get-Command hyper-v\get-vm).Module.Name
The previous code will report the module as Hyper-v.
You can avoid this problem with performing the query as follows:
#(Get-Module -ListAvailable hyper-v).Name | Get-Unique
You will get the casing that is consistent between all version of PowerShell and Build versions of Windows.
Expected:
But this is sometimes seen:
Note: There are two libraries installed for the modules, which you can see with Get-Module -ListAvailable. Maybe these versions or the Windows build or release version differs? At least, this information is relevant since Windows 10, as they are not fixed anymore. They might help with pinpointing (added comment).

Register Powershell SnapIn to specific version of Powershell

I am trying to run an Exchange Management Shell script via managed code.
Our PowerShell version is version 4.
I am trying to use the runspaceConfiguration.AddPSSnapin() method to add the exchange SnapIn Microsoft.Exchange.Management.Powershell.E2010, however this line fails, giving :
no snapins have been registered for Powershell version 4.
A quick Get-PSSnapIn -registered shows the SnapIn is installed but registered to PS version 1.
In regEdit, I have found the registry key for the snapIn for PowerShell v1, used the export function to script the key, changed the target version to 4 and ran the export command, which has created the new key '4' under PowerShell as expected, with the snapin details copied across. However I still get the 'no snapins have been registered for Powershell version 4.', even though the key is there in the registry.
Is there something else I should be doing to register the snapin to PowerShell version 4?
Thank you for your time.
You are correct in that this is a 32/64 bit build issue. After I set the target build to x64 in VS and redeployed, all magically seemed to work fine.
The error message regarding Powershell versions is a massive Red Herring in this case.

Installing PowerShell module persistently for all users

I'm installing a PowerShell module via Octopus Deploy onto a number of different servers. For testing purposes, I went with the guidance of Microsoft's documentation for installing PowerShell Modules.
This worked fine, but as the documentation stated, my changes would be visible only for the current session. That is, if I were to do the following:
$modulePath = [Environment]::GetEnvironmentVariable("PSModulePath", [EnvironmentVariableTarget]::Machine)
# More practically, this would be some logic to install only if not present
$modulePath += ";C:\CustomModules"
[Environment]::SetEnvironmentVariable("PSModulePath", $modulePath, [EnvironmentVariableTarget]::Machine)
When running this installer automatically on tentacle servers, future PowerShell sessions do not appear to see the newly installed modules.
How can I install a PowerShell module in a profile agnostic way so that every PowerShell session started can see it?
PowerShell can only "see" modules installed in one of the directories listed in $env:PSModulePath. Otherwise you'll have to import the module with its full path.
To make a new module visible to all users you basically have two options:
Install the module to the default system-wide module directory (C:\Windows\system32\WindowsPowerShell\v1.0\Modules).
Modify the system environment so that PSModulePath variable already contains your custom module directory (e.g. via a group policy preference).
The latter will only become effective for PowerShell sessions started after the modification was made, though.
This profile applies to all users and all shells.
%windir%\system32\WindowsPowerShell\v1.0\profile.ps1
After taking the steps you spelled out in your question (which I think is the general way to go), I found two ways to get the new module source recognized by Powershell:
Restart the machine. (Works every time.)
Reset the PSModulePath in each open session.
$env:PSModulePath=[Environment]::GetEnvironmentVariable("PSModulePath", "Machine")
I found this was necessary to run in both normal and elevated prompts to get this to work without restarting in each type of prompt. (See also the conversation # Topic: PSModulePath.)