Wednesday, 19 August 2015

Import PowerShell Module from a UNC path share

Recently I have put all my most commonly used PowerShell scripts into a Module. By bundling them together into a module, I remove the need for individual script files to be maintained and updated on all servers in the network.

Doing this has caused a few problems of its own, so here is the process I used to get it working.

The first thing I did was copy the module to a shared location that can be accessed easily by everything on the network.

Next, anything that needs to use my module needs to be made aware of it. There are 2 steps to this process.

1. Edit the environment variable “PSModulePath” to include the path to the module
2. Import the module


Both steps can be accomplished by editing the PowerShell profile. There are several PowerShell profiles; I have chosen to use the profile for All Users, All Hosts. This ensures the module will be available no matter how PowerShell is used. This is important for me because as well as running commands locally I also call commands using external applications like Nagios.
More information on PowerShell profiles can be found here:

The All Users, All Hosts profile is located here:
C:\windows\system32\WindowsPowerShell\v1.0\profile.ps1


The profile.ps1 file will not exist by default, so it is usually necessary to create it (new text file called profile.ps1)

1. Edit the environment variable “PSModulePath” to include the path to the module This done by adding the following line to the PowerShell profile:


$env:psmodulepath = $env:psmodulepath +";\\ServerShare\Folder\modules"

2. Import the module
This done by adding the following line to the PowerShell profile:

Import-Module ModuleName

Launching PowerShell will give you an error message at this point. This is because PowerShell sees the module as coming from an external source and doesn't trust it. PowerShell uses Internet Explorer's Zone Policy to see if it trusts the file, script or module you are attempting to run.

This can be manually edited through:

Internet Options >> Security tab >> Local Intranet >> Sites
Here we can add the external source as a trusted site, \\ServerShare

Doing this will only take effect for the currently logged on user, and will still cause an error when an external program attempts to run something using PowerShell - eg. Nagios

To get around this we must add the trusted site to the local machine's trusted zone policy. This can be done by creating two new registry keys.

HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\ServerShare

Name: file
Type: DWORD
Value: 1

and

HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\ServerShare

Name: file
Type: DWORD
Value: 1

I have a lot of servers that require this change, so I wrote some PowerShell that will do it for me, here is the script:

$registryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\ServerShare"
$registryPath1 = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\ServerShare"
$name = "file"
$value = "1"

If (!(test-path $registryPath )) {
New-Item -Path $registryPath -Force | Out-Null
New-ItemProperty -Path $registryPath -Name $name -Value $value -PropertyType DWORD -Force | Out-Null
}
If (!(test-path $registryPath1 )) {
New-Item -Path $registryPath1 -Force | Out-Null
New-ItemProperty -Path $registryPath1 -Name $name -Value $value -PropertyType DWORD -Force | Out-Null
}

Using these settings let me use all the commands that are exposed by my module and I can leave the execution policy set at Remote Signed.

Below are some good articles that helped me out with this process:

Running PowerShell Scripts From An UNC Path (Share)
Signing PowerShell Scripts
Can't import a module from a UNC path
Update or Add Registry Key Value with PowerShell


UPDATE - 19-10-2015

I've been using this process quite a bit, and found that it doesn't work in all circumstances. For newer versions of Internet Explorer, there is an extra step to editing the Local Intranet zones policy:
Internet Options >> Security tab >> Local Intranet >> Sites >> Advanced

When using these later versions of IE, the registry changes don't work, and the module on a UNC path still causes an error when the profile tries to import it. I can stop the error from being displayed by changing the Execution Policy to Bypass, however, I wasn't keen on implementing that as a solution across our estate. Instead I'm now using a different method.

Copy-Item \\ServerShare\Folder\modules\ModuleName -Destination D:\scripts\modules -Recurse -Force

$env:psmodulepath = $env:psmodulepath +";D:\scripts\modules"

Import-module ModuleName


This takes a copy of the module and puts it into a local folder. The module is then imported from this local folder, which removes the need for PowerShell to treat it as a remote script.