Take the 2-minute tour ×
Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

I have some Powershell scripts for setting up IIS web applications, message queues, etc.

They use a set of shared function libraries we've created - so each script starts with the line

. .\common.ps1

to reference the shared library. The shared library contains a set of functions like Create-IisApplicationPool, Create-MessageQueue and the like, which are then called from the actual script. The problem with these scripts is that you need to log on via Remote Desktop and run them locally, so I'm writing some new scripts for deploying code to an Amazon EC2 instance, and using Powershell remoting to invoke them locally.

What I can't work out is how to make this shared library available in a remote Powershell session.

Here's a simple example:

functions.ps1

function Add-Title([string] $name) {
    "Mr. $name"
}

function Say-HelloWorld([string] $name = "World") {
    $computerName = $env:COMPUTERNAME
    $greeting = Add-Title($name)
    Write-Host "Hello, $greeting ($computerName)"
}

example.ps1

. ./functions.ps1


$remoteUsername = "username"
$remotePassword = "password"
$remoteHostname = "172.16.0.100"

$securePassword = ConvertTo-SecureString -AsPlainText -Force $remotePassword
$cred = New-Object System.Management.Automation.PSCredential $remoteUsername, $securePassword


Say-HelloWorld("Spolsky")

Running locally, this works great - and says "Hello, Mr. Spolsky (DYLAN_PC)" as expected.

Now, if I replace the Say-HelloWorld call with this remote script invocation:

Invoke-Command -computerName $remoteHostname -Credential $cred -ScriptBlock {
    Say-HelloWorld("Spolsky")
}

I get the Powershell error:

The term 'Say-HelloWorld' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is co rrect and try again. + CategoryInfo : ObjectNotFound: (Say-HelloWorld:String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException

Clearly, the remote session can't see the functions that have been imported locally.

For simple functions, this syntax works:

Invoke-Command -computerName $remoteHostname -Credential $cred -ScriptBlock ${function:Add-Title } -argumentlist "Spolsky"

but this fails for any function which depends on other functions.

I've tried various things using PS-ExportSession and trying to pass a -Session argument to Invoke-Command, but can't find any way of capturing local functions and their dependencies in a module that can be imported into a remote session. Any help gratefully received!

share|improve this question

3 Answers 3

You say each script begins with . .\common.ps1, but I don't see it in your example script. Regardless, if you need to use the functions in common.ps1, then you have to put it somewhere that the remote machine can access.

You can use the redirected drives in Powershell like you would in an RDP session:

New-PSSession SERVER1 -Cred $creds | Enter-PSSession
. \\tsclient\c\common.ps1

Or you could put common.ps1 on a network share:

. \\Server1\share\common.ps1

Or you can just paste the contents on common.ps1 into every script.

Or you can put common.ps1 on the local file system of the remote machine.

When the Powershell parser on the remote machine sees ". .\common.ps1" it's trying to load common.ps1 from the current working directory, which is probably the home directory of whatever user has established the remote PS session.

share|improve this answer
    
Sorry. Example's updated to make the function invocation a little clearer. –  Dylan Beattie Dec 3 '12 at 17:59

I would have expected you need to copy functions.ps1 over before dot-sourcing it. In fact I'd probably copy both scripts over to the same directory (e.g. via the C$ admin share to \\server\C$\foo) and then remotely invoke C:\foo\example.ps1 using Invoke-Command.

share|improve this answer

I had exactly the same problem. I assume it is to do with remote invocations running in the context of the remote caller. I managed to work round the problem by providing a full local path to the target library as a variable, and then dot-sourcing the library via the variable:

function Get-ScriptDirectory
{
    return Split-Path $script:MyInvocation.MyCommand.Path
}

function GetLocalFileName
{
    param([string]$filename)    
    return [system.io.path]::Combine((Get-ScriptDirectory),$filename)
}

# dot-source the library using full local name to get round problem with remote invocation
$MYLIBRARY = GetLocalFileName "mylibrary.ps1"
. $MYLIBRARY 
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.