Как импортировать пользовательский модуль PowerShell в удаленный сеанс?


Я разрабатываю пользовательский модуль PowerShell, который я хотел бы использовать в контексте удаленного сеанса на другом компьютере. Следующий код (который, очевидно, не работает) объясняет, чего я пытаюсь достичь:

import-module .MyCustomModule.psm1
$session = new-pssession -computerName server01
invoke-command -session $session -scriptblock { 
  <# use function defined in MyCustomModule here #> 
}

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

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

Если это возможно, то как?

Версия PowerShell в настоящее время не является ограничением - если решение доступно только в PS 3.0 - я могу жить с этим.

4 29

4 ответа:

Было несколько замечательных комментариев к этому вопросу, и я потратил некоторое время, исследуя различные способы подхода к проблеме.

Начнем с того, что то, о чем я изначально просил, невозможно. Я имею в виду, что если вы идете по пути модуля, то модуль должен физически присутствовать на целевой машине, чтобы иметь возможность Import-Module входить в удаленный сеанс.

Чтобы абстрагироваться от моего вопроса дальше, я пытаюсь создать многоразовую платформу на основе PowerShell для развертывания продукта. Это произойдет это означает, что мы поощряем людей запускать некоторые сценарии на локальной машине для развертывания на каком-либо удаленном сервере. Насколько я изучил эту область, есть два возможных пути, которые дружественны здравому смыслу.

Модульный подход

Следующий процесс:

  • поместите каждый логически отличающийся элемент функциональности в модуль PowerShell (*.psm1)
  • распределите модуль на удаленную машину и расширьте переменную PSModulePath чтобы включить новые модули location
  • на клиентской машине создайте новый сеанс на удаленном сервере и используйте Invoke-Command -Session $s -ScriptBlock {...}
  • в блоке скрипта start from Import-Module CustomModule - он будет искать CustomModule на удаленной машине и, очевидно, найдет его

Преимущества

Вот причины, по которым стоит любить этот подход:

  • следствие традиционной роли модуля-облегчение создания многоразовых библиотек
  • согласно отличная книгаWindows PowerShell in Action , "модули можно использовать для создания доменных приложений". Насколько я понимаю, это может быть достигнуто путем объединения вложенности модулей и смешивания скриптовых / бинарных модулей, чтобы предоставить интуитивно понятный интерфейс, специфичный для определенной области. В принципе, это то, что я ценю больше всего для цели PowerShell-based deployment framework

Недостатки

Важно учитывать следующее: рассмотрение:

  • Вы должны найти способ доставки пользовательских модулей на удаленную машину. Я играл с NuGet , и я не уверен, что он хорошо подходит для этой задачи, но есть и другие варианты, например, MSI installer или plain xcopy из общей папки. Кроме того, механизм доставки должен поддерживать обновление / понижение и (предпочтительно) установку нескольких экземпляров, но это больше связано с моей задачей, чем с проблемой в общие

Скриптовый подход

Следующий процесс:

  • поместите каждый логически отличающийся фрагмент функциональности в отдельный скрипт PowerShell ( * . ps1)
  • на клиентской машине создайте новый сеанс для удаленного сервера и используйте Invoke-Command -Session $s -FilePath .\myscript.ps1 для загрузки функций, определенных в скрипте, в удаленный сеанс
  • используйте другой Invoke-Command -Session $s -ScriptBlock {...} и обратитесь к вашим пользовательским функциям - они будут там в сеансе

Преимущества

В следующие хорошие моменты этого подхода:

  • это просто - вам не нужно знать об особенностях модуля. Просто напишите простые сценарии PowerShell, и все
  • Вам не нужно ничего доставлять на удаленную машину - это делает решение еще более простым и менее подверженным ошибкам в обслуживании

Недостатки

Конечно, это не идеально:

  • там меньше контроля над решением: например, если вы "импортируете" набор функции к сессии, все они " импортированы "и видны пользователю, поэтому никакой" инкапсуляции " и т. д. я уверен, что многие решения могут жить с этим, поэтому не принимайте решения, основываясь только на этом пункте
  • функциональность в каждом файле должна быть самодостаточной - любой точечный источник или импорт модуля оттуда будет искать удаленную машину, а не локальную
Наконец, я должен сказать, что удаленная машина все еще должна быть подготовлена к удалению. Вот что я хочу сказать. имею в виду:
  • политика выполнения должна быть изменена на что-то, потому что она ограничена по умолчанию: Set-ExecutionPolicy Unrestricted
  • удаленное взаимодействие PowerShell должно быть включено: Enable-PSRemoting
  • учетная запись, которую запускает скрипт, должна быть добавлена к локальным администраторам удаленного сервера
  • если вы планируете получить доступ к общим файлам в удаленном сеансе, убедитесь, что вы знаете о многоходовой аутентификации и выполните соответствующие действия
  • убедитесь, что ваш антивирус-ваш друг. и не отправляет вас в PowerShell hell

Вот еще один подход: воссоздайте модуль в удаленном сеансе, без копирования каких-либо файлов.

Я не делал попыток справиться с зависимостями между модулями, но это, кажется, работает нормально для простых автономных модулей. Он полагается на модуль, доступный в локальной сессии, так как это упрощает определение экспорта, но с небольшим количеством дополнительной работы он также будет работать с файлом модуля.

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;
}

Я не верю, что это поддерживается прямо из коробки без каких-либо "хаков". Разумным шагом, вероятно, было бы поместить модуль в общедоступное место, например на файловый сервер, и импортировать его на сервер, когда он вам понадобится. Пример:

$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 #> 
}

Как насчет того, чтобы сделать scriptblock из вашей пользовательской функции и отправить его на серверы terget с помощью Invoke-command

Import-module YourModule
$s = [scriptblock]::Create($(get-item Function:\Your-ModuleFunction).Definition)

Invoke-Command -ScriptBlock $s -Computername s1,s2,sn