Reputation: 32240
I'm developing a custom PowerShell module, which I'd like to use in context of a remote session to a different computer. The following code (which obviously doesn't work) explains what I'm trying to achieve:
import-module .\MyCustomModule.psm1
$session = new-pssession -computerName server01
invoke-command -session $session -scriptblock {
<# use function defined in MyCustomModule here #>
}
The first question is whether it is at all possible to achieve this scenario? I mean I would only like my custom module to be physically present on my machine, not on remote server.
I have found this thread, but I didn't manage it to work - it doesn't allow creating a session from remote machine back to the local one. Probably, I faced with the configuration limitations mentioned somewhere in the comments to that thread... Besides, the author mentioned the performance implications which is critical for my solution...
If that's possible, then how?
The version of PowerShell is currently not a constraint - if the solution is only available in PS 3.0 - I can live with this.
Upvotes: 42
Views: 51074
Reputation: 1
Not so elegant solution but very simple. I needed to send functions to a VM but it would be a minor tweak to send to remote session instead.
I have a custom PS module in callers (host) C drive: C:\myModule.psm1
function Write-OutputString {
param(
$OutputString
)
$OutputString
}
In this example the module is sent as string to the remote session:
$ScriptOnHost = {
param(
$Module
)
# Initialize all functions in module
Invoke-Expression $Module
# Call function from module
Write-OutputString -OutputString "Hello World!"
}
$VMSession = New-PSSession -VMName $VMName -Credential $VMCredentials
# Module as string
$ModuleOnHost = Get-Content "C:\myModule.psm1" -Raw
Invoke-Command -Session $VMSession -ScriptBlock $ScriptOnHost -ArgumentList $ModuleOnHost
All functions inside the module are now available in the remote session. Parameters to the functions can be provided statically inside the scriptblock or added as parameters to the scripblock itself and passed along.
Upvotes: 0
Reputation: 413
Since PS 5.0, I think there is now another cleaner way:
Utilise Copy-Item's ToSession parameter to copy the local module to the remote machine.
This doesn't involve the disadvantages of previous solutions:
Example usage:
$s = New-PSSession MyTargetMachine
Get-Module MyLocalModule | Import-LocalModuleToRemoteSession -Session $s -Force
# Show module is loaded
Invoke-Command $s -ScriptBlock { Get-Module }
Import-LocalModuleToRemoteSession function
Note it doesn't load the module dependencies
<#
.SYNOPSIS
Imports a loaded local module into a remote session
.DESCRIPTION
This script copies a module's files loaded on the local machine to a remote session's temporary folder and imports it, before removing the temporary files.
It does not require any shared folders to be exposed as it uses the default Copy-To -ToSession paramter (added in PS 5.0).
#>
function Import-LocalModuleToRemoteSession
{
[CmdletBinding()]
param(
# Module to import
[Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName,Mandatory)]
[System.Management.Automation.PSModuleInfo]$ModuleInfo,
# PSSession to import module to
[Parameter(Mandatory)]
[System.Management.Automation.Runspaces.PSSession]
$Session,
# Override temporary folder location for module to be copied to on remote machine
[string]
$SessionModuleFolder=$null,
[switch]
$Force,
[switch]
$SkipDeleteModuleAfterImport
)
begin{
function New-TemporaryDirectory {
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
New-Item -ItemType Directory -Path (Join-Path $parent $name)
}
}
process{
if( [string]::IsNullOrWhiteSpace($SessionModuleFolder) ){
Write-Verbose "Creating temporary module folder"
$item = Invoke-Command -Session $Session -ScriptBlock ${function:New-TemporaryDirectory} -ErrorAction Stop
$SessionModuleFolder = $item.FullName
Write-Verbose "Created temporary folder $SessionModuleFolder"
}
$directory = (Join-Path -Path $SessionModuleFolder -ChildPath $ModuleInfo.Name)
Write-Verbose "Copying module $($ModuleInfo.Name) to remote folder: $directory"
Copy-Item `
-ToSession $Session `
-Recurse `
-Path $ModuleInfo.ModuleBase `
-Destination $directory
Write-Verbose "Importing module on remote session @ $directory "
try{
Invoke-Command -Session $Session -ErrorAction Stop -ScriptBlock `
{
Get-ChildItem (Join-Path -Path ${Using:directory} -ChildPath "*.psd1") `
| ForEach-Object{
Write-Debug "Importing module $_"
Import-Module -Name $_ #-Force:${Using:Force}
}
if( -not ${Using:SkipDeleteModuleAfterImport} ){
Write-Debug "Deleting temporary module files: $(${Using:directory})"
Remove-Item -Force -Recurse ${Using:directory}
}
}
}
catch
{
Write-Error "Failed to import module on $Session with error: $_"
}
}
}
Upvotes: 2
Reputation: 1
Thanks for this thread it was helpfull….
But i actually rewrote the function.
Be aware, that nether the original function in this post or this rewritten function includes module manifest data. So you cant rely on version checks on the module.
function Import-ModuleRemotely {
Param (
[string] $moduleName,
[System.Management.Automation.Runspaces.PSSession] $session
)
Import-Module $moduleName
$Script = @"
if (get-module $moduleName)
{
remove-module $moduleName;
}
New-Module -Name $moduleName { $($(Get-Module $moduleName).Definition) } | Import-Module
"@
Invoke-Command -Session $Session -ScriptBlock {
Param($Script)
. ([ScriptBlock]::Create($Script))
Get-Module
} -ArgumentList $Script
}
Upvotes: 0
Reputation: 4477
Here's another approach: Recreate the module in a remote session, without copying any files.
I've made no attempt to cope with dependencies between modules, but this seems to work ok for simple self contained modules. It relies on the module being available in the local session, as this makes determining exports easier, but with a bit of extra work it would also work with a module file.
function Import-ModuleRemotely([string] $moduleName,[System.Management.Automation.Runspaces.PSSession] $session)
{
$localModule = get-module $moduleName;
if (! $localModule)
{
write-warning "No local module by that name exists";
return;
}
function Exports([string] $paramName, $dictionary)
{
if ($dictionary.Keys.Count -gt 0)
{
$keys = $dictionary.Keys -join ",";
return " -$paramName $keys"
}
}
$fns = Exports "Function" $localModule.ExportedFunctions;
$aliases = Exports "Alias" $localModule.ExportedAliases;
$cmdlets = Exports "Cmdlet" $localModule.ExportedCmdlets;
$vars = Exports "Variable" $localModule.ExportedVariables;
$exports = "Export-ModuleMember $fns $aliases $cmdlets $vars;";
$moduleString= @"
if (get-module $moduleName)
{
remove-module $moduleName;
}
New-Module -name $moduleName {
$($localModule.Definition)
$exports;
} | import-module
"@
$script = [ScriptBlock]::Create($moduleString);
invoke-command -session $session -scriptblock $script;
}
Upvotes: 7
Reputation: 1
What about making scriptblock out of your custom function and sending it off to terget servers using Invoke-command
Import-module YourModule
$s = [scriptblock]::Create($(get-item Function:\Your-ModuleFunction).Definition)
Invoke-Command -ScriptBlock $s -Computername s1,s2,sn
Upvotes: 0
Reputation: 32240
There were some great comments to the question, and I've spent some time investigating various ways to approach the problem.
To begin with, what I've initially asked for is not possible. I mean, if you go the module way, then the module should be physically present on a target machine to be able to Import-Module
into remote session.
To abstract my question further, I'm trying to create a reusable PowerShell-based framework for the product deployments. It's going to be a push-manner deployments, meaning that we encourage people to run some scripts on a local machine to deploy to some remote server. As far as I investigated the area, there are two possible ways which are friendly to the common sense.
The process to follow:
*.psm1
)PSModulePath
variable to include the new modules locationInvoke-Command -Session $s -ScriptBlock {...}
Import-Module CustomModule
- it will search the CustomModule
on a remote machine and obviously will find itThe following are the reasons to love this approach for:
The following is important to take into consideration:
xcopy
from the shared folder. Besides, the delivery mechanism should support upgrade / downgrade and (preferably) multi-instance installations, but that's more related to my task than to the problem in generalThe process to follow:
Invoke-Command -Session $s -FilePath .\myscript.ps1
to load the functions defined in a script to the remote sessionInvoke-Command -Session $s -ScriptBlock {...}
and refer to your custom functions - they will be there in a sessionThe following are good points of this approach:
Sure, it's not ideal:
Finally, I should say that remote machine still needs to be prepared for the remoting. This is what I mean:
Set-ExecutionPolicy Unrestricted
Enable-PSRemoting
Upvotes: 48
Reputation: 54821
I don't believe this is supported right of the box without any "hacks". The smart move would probably be to put the module on a public location like a fileserver and import it on the server when you need it. Ex:
$session = new-pssession -computerName server01
invoke-command -session $session -scriptblock {
#Set executionpolicy to bypass warnings IN THIS SESSION ONLY
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
#Import module from public location
Import-Module \\fileserver\folders\modulelocation...
<# use function defined in MyCustomModule here #>
}
Upvotes: 3