Как включить локально определенную функцию при использовании PowerShell Invoke-Command для удаленного взаимодействия?

Я чувствую, что упускаю что-то, что должно быть очевидным, но я просто не могу понять, как это сделать.

У меня есть скрипт ps1, в котором определена функция. Он вызывает функцию, а затем пытается использовать ее удаленно:

function foo
{
    Param([string]$x)

    Write-Output $x
}

foo "Hi!"

Invoke-Command -ScriptBlock { foo "Bye!" } -ComputerName someserver.example.com -Credential [email protected]

Этот короткий пример сценария печатает «Привет!» а затем вылетает со словами: «Термин« foo »не распознается как имя командлета, функции, файла сценария или работающей программы».

Я понимаю, что функция не определена на удаленном сервере, потому что ее нет в ScriptBlock. Я мог бы переопределить его там, но я бы не хотел. Я хотел бы определить функцию один раз и использовать ее локально или удаленно. Есть ли хороший способ сделать это?


person David Hogue    schedule 06.07.2012    source источник


Ответы (4)


Вам нужно передать саму функцию (не вызов функции в ScriptBlock).

У меня была такая же потребность только на прошлой неделе, и я нашел это обсуждение SO< /а>

Таким образом, ваш код станет:

Invoke-Command -ScriptBlock ${function:foo} -argumentlist "Bye!" -ComputerName someserver.example.com -Credential [email protected]

Обратите внимание, что с помощью этого метода вы можете передавать параметры в свою функцию только позиционно; вы не можете использовать именованные параметры, как при локальном запуске функции.

person alroc    schedule 06.07.2012
comment
Хорошо, это похоже на то, что я читал. Итак, сделайте еще один шаг: есть ли хороший способ включить функцию и некоторые дополнительные строки скрипта, которые используют эту функцию? - person David Hogue; 07.07.2012
comment
Я еще не проверял это, но если вы хотите отформатировать вывод функции (например), это должно работать: -ScriptBlock {$function:foo|format-table -auto}. По сути, любой ScriptBlock может быть любым фрагментом действительного кода PowerShell, поэтому, если вы правильно отформатируете его (или используете точку с запятой в конце каждой строки), все будет хорошо. Ранее сегодня я возился с `measure-command {$x=[xml](get-content file.xml);$x.selectsinglenode(//thing);}, например. - person alroc; 07.07.2012

Вы можете передать определение функции в качестве параметра, а затем переопределить функцию на удаленном сервере, создав блок сценария, а затем используя его точечный источник:

$fooDef = "function foo { ${function:foo} }"

Invoke-Command -ArgumentList $fooDef -ComputerName someserver.example.com -ScriptBlock {
    Param( $fooDef )

    . ([ScriptBlock]::Create($fooDef))

    Write-Host "You can call the function as often as you like:"
    foo "Bye"
    foo "Adieu!"
}

Это устраняет необходимость иметь дублирующую копию вашей функции. Вы также можете передать более одной функции таким образом, если вы так склонны:

$allFunctionDefs = "function foo { ${function:foo} }; function bar { ${function:bar} }"
person JamesQMurphy    schedule 30.05.2014
comment
Именно то, что я искал! Ваше решение в сочетании с $Using:Var помогло :) Большое спасибо! - person DarkLite1; 30.01.2015
comment
Он работает до тех пор, пока имя вашей функции не содержит - например, Get-Foo. Я не нахожу синтаксиса. - person Yann Greder; 13.03.2021

Вы также можете поместить функции и скрипт в файл (foo.ps1) и передать его в Invoke-Command, используя параметр FilePath:

Invoke-Command –ComputerName server –FilePath .\foo.ps1

Файл будет скопирован на удаленные компьютеры и выполнен.

person Keith Hill    schedule 01.07.2015
comment
Это лучший вариант. Уменьшает беспорядок и легко. Напишите сценарий так, как вы хотите, чтобы он выполнялся, и вуаля! - person Pranav Jituri; 23.06.2017

Хотя это старый вопрос, я хотел бы добавить свое решение.

Достаточно забавно, что список параметров блока сценария в функциональном тесте не принимает аргумент типа [блок сценария] и поэтому требует преобразования.

Function Write-Log 
{
    param(
        [string]$Message
    )

    Write-Host -ForegroundColor Yellow "$($env:computername): $Message"
}

Function Test
{
    $sb = {
        param(
            [String]$FunctionCall
        )

        [Scriptblock]$WriteLog = [Scriptblock]::Create($FunctionCall) 
        $WriteLog.Invoke("There goes my message...")               
    }

    # Get function stack and convert to type scriptblock 
    [scriptblock]$writelog = (Get-Item "Function:Write-Log").ScriptBlock 

    # Invoke command and pass function in scriptblock form as argument 
    Invoke-Command -ComputerName SomeHost -ScriptBlock $sb -ArgumentList $writelog
}

Test

Другая возможность — передать хэш-таблицу в наш блок скриптов, содержащую все методы, которые вы хотели бы иметь доступными в удаленном сеансе:

Function Build-FunctionStack 
{
    param([ref]$dict, [string]$FunctionName)

    ($dict.Value).Add((Get-Item "Function:${FunctionName}").Name, (Get-Item "Function:${FunctionName}").Scriptblock)
}

Function MyFunctionA 
{
    param([string]$SomeValue)

    Write-Host $SomeValue
}

Function MyFunctionB
{
    param([int]$Foo)

    Write-Host $Foo
}

$functionStack = @{}

Build-FunctionStack -dict ([ref]$functionStack) -FunctionName "MyFunctionA"
Build-FunctionStack -dict ([ref]$functionStack) -FunctionName "MyFunctionB" 

Function ExecuteSomethingRemote
{
    $sb = {
        param([Hashtable]$FunctionStack)

        ([Scriptblock]::Create($functionStack["MyFunctionA"])).Invoke("Here goes my message");
        ([Scriptblock]::Create($functionStack["MyFunctionB"])).Invoke(1234);

    }

    Invoke-Command -ComputerName SomeHost -ScriptBlock $sb -ArgumentList $functionStack
}

ExecuteSomethingRemote
person Matthias Güntert    schedule 11.04.2018